From f58b2afcaaf1977f02c590a8832bb67f77ee7be3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 18 Jul 2015 19:38:52 +0200 Subject: changed switch widget --- .../conversations/ui/adapter/AccountAdapter.java | 6 +- .../eu/siacs/conversations/ui/widget/Switch.java | 70 ++++++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/ui/widget/Switch.java (limited to 'src/main/java/eu') 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 782a1231..226b1920 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -6,6 +6,8 @@ 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; @@ -14,7 +16,6 @@ import android.widget.ArrayAdapter; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.TextView; -import android.widget.Switch; public class AccountAdapter extends ArrayAdapter { @@ -53,8 +54,7 @@ public class AccountAdapter extends ArrayAdapter { } final Switch tglAccountState = (Switch) view.findViewById(R.id.tgl_account_status); final boolean isDisabled = (account.getStatus() == Account.State.DISABLED); - tglAccountState.setOnCheckedChangeListener(null); - tglAccountState.setChecked(!isDisabled); + tglAccountState.setChecked(!isDisabled,false); tglAccountState.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) { diff --git a/src/main/java/eu/siacs/conversations/ui/widget/Switch.java b/src/main/java/eu/siacs/conversations/ui/widget/Switch.java new file mode 100644 index 00000000..c72e760e --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/widget/Switch.java @@ -0,0 +1,70 @@ +package eu.siacs.conversations.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import com.kyleduo.switchbutton.SwitchButton; + +import eu.siacs.conversations.Config; + +public class Switch extends SwitchButton { + + private int mTouchSlop; + private int mClickTimeout; + private float mStartX; + private float mStartY; + private OnClickListener mOnClickListener; + + public Switch(Context context) { + super(context); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); + } + + public Switch(Context context, AttributeSet attrs) { + super(context, attrs); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); + } + + public Switch(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); + } + + @Override + public void setOnClickListener(OnClickListener onClickListener) { + this.mOnClickListener = onClickListener; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isEnabled()) { + float deltaX = event.getX() - mStartX; + float deltaY = event.getY() - mStartY; + int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + mStartX = event.getX(); + mStartY = event.getY(); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + float time = event.getEventTime() - event.getDownTime(); + if (deltaX < mTouchSlop && deltaY < mTouchSlop && time < mClickTimeout) { + if (mOnClickListener != null) { + this.mOnClickListener.onClick(this); + } + } + break; + default: + break; + } + return true; + } + return super.onTouchEvent(event); + } +} -- cgit v1.2.3 From 0166ced46cc6663a9fa86b60588d440ca8a7af7c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 19 Jul 2015 13:36:02 +0200 Subject: bugfix: accept status code 201 on http upload --- src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index dd842754..a3ab8dab 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -157,7 +157,7 @@ public class HttpUploadConnection implements Transferable { os.close(); is.close(); int code = connection.getResponseCode(); - if (code == 200) { + if (code == 200 || code == 201) { Log.d(Config.LOGTAG, "finished uploading file"); Message.FileParams params = message.getFileParams(); if (key != null) { -- cgit v1.2.3 From 5c017e5186d9359bbfca0a05dcac8ed22516800d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 19 Jul 2015 14:25:30 +0200 Subject: bugfix: use sendIqPacket method in service instead of invoking XmppConnection directly --- .../java/eu/siacs/conversations/services/XmppConnectionService.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index ee212aed..f5c54adf 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -830,9 +830,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } else { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster"); } - iqPacket.query(Xmlns.ROSTER).setAttribute("ver", - account.getRosterVersion()); - account.getXmppConnection().sendIqPacket(iqPacket, mIqParser); + iqPacket.query(Xmlns.ROSTER).setAttribute("ver",account.getRosterVersion()); + sendIqPacket(account,iqPacket,mIqParser); } public void fetchBookmarks(final Account account) { -- cgit v1.2.3 From 9b70c7e68ccf3b1efdaff782f14b0549415f1339 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 19 Jul 2015 14:51:04 +0200 Subject: bugfix: don't crash if aes key could not be set before jingle transfer --- .../java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/main/java/eu') 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 3c355b57..65cafe79 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -368,7 +368,10 @@ public class JingleConnection implements Transferable { message, false); if (message.getEncryption() == Message.ENCRYPTION_OTR) { Conversation conversation = this.message.getConversation(); - this.mXmppConnectionService.renewSymmetricKey(conversation); + if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key"); + cancel(); + } content.setFileOffer(this.file, true); this.file.setKey(conversation.getSymmetricKey()); } else { -- cgit v1.2.3 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/eu') 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 00000000..8e300248 --- /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 00000000..b11670e4 --- /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 f472361f..38312566 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 e546f214..2f9b375d 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/eu') 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 8e300248..eae7a9ab 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 00000000..663b42b5 --- /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 b11670e4..4b87fc5c 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 38312566..7a2dc3f7 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 2f9b375d..45b55e49 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 f5c54adf..bed9267b 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/eu') 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 eae7a9ab..865f903a 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 79626511..69bb1803 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 47915e3f..6daadc2a 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 bc1148d9..e6032e0c 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 6039d395..00f96885 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 d46ff195..bf4f3ad4 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 32657c66..dc5a68f6 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 e32811af..628f0d93 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/eu') diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index bed9267b..1ebe3a03 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 c190caed..fb04946a 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 96abf65b..ff1355c3 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 d254ece7..ec50ea54 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 7c994c31..eebeb040 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/eu') 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 865f903a..5c4b34a4 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 4b87fc5c..e1b95650 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 45b55e49..240d5223 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 6daadc2a..5b3bde6a 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 e6032e0c..0b6a7c61 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 00f96885..df143a41 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 bf4f3ad4..de705730 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 af0e2fa8..ee6c7636 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 1ebe3a03..9a4cc276 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 ff1355c3..e4ce4a0f 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/eu') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index ee6c7636..966734ba 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/eu') 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 5c4b34a4..d788ed06 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 966734ba..c70ffad2 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/eu') 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 d788ed06..8879a0fe 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/eu') 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 8879a0fe..22e959eb 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 5b3bde6a..0bef8853 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 df143a41..93551787 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 9a4cc276..f96e5d7e 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/eu') 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 22e959eb..e4c0480a 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/eu') 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 e4c0480a..54394dd2 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 289ed4ea..2efd8a29 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 0b6a7c61..b0727690 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 de705730..31f70b97 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 f96e5d7e..8b716121 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/eu') 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 e1b95650..01b06c22 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 336af972..b1dffc82 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/eu') 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 54394dd2..6a493e54 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/eu') 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 6a493e54..1b591f78 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/eu') 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 1b591f78..470c5206 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 0bef8853..19c1d4f7 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/eu') 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 470c5206..d4089b20 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 01b06c22..06dd2cda 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 31f70b97..cc878e7f 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/eu') 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 d4089b20..d79fa1bf 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 8b716121..3e443d75 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/eu') 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 d79fa1bf..420c75b5 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/eu') 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 420c75b5..cdd8d85d 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/eu') 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 cdd8d85d..d37879c3 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 93551787..c147978e 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/eu') 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 d37879c3..faa0e5ad 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/eu') diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 240d5223..fdb5f932 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/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 2777b814..33861f82 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 2dec203d..c7c9ac42 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/eu') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index c70ffad2..a3868a1d 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/eu') 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 faa0e5ad..2b0954c6 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 cc878e7f..0dc8d521 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 37b214a8a853dfdc1760bc69d9213484e519832f Mon Sep 17 00:00:00 2001 From: Christian S Date: Sun, 19 Jul 2015 14:03:46 +0200 Subject: show contact details in conference details ... --- .../java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 07b8819d..614a6648 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -274,6 +274,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers name = user.getJid().toBareJid().toString(); } menu.setHeaderTitle(name); + MenuItem showContactDetails = menu.findItem(R.id.action_contact_details); MenuItem startConversation = menu.findItem(R.id.start_conversation); MenuItem giveMembership = menu.findItem(R.id.give_membership); MenuItem removeMembership = menu.findItem(R.id.remove_membership); @@ -282,6 +283,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room); MenuItem banFromConference = menu.findItem(R.id.ban_from_conference); startConversation.setVisible(true); + showContactDetails.setVisible(true); if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) && self.getAffiliation().outranks(user.getAffiliation())) { if (mAdvancedMode) { @@ -309,6 +311,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { + case R.id.action_contact_details: + switchToContactDetails(mSelectedUser.getContact()); + return true; case R.id.start_conversation: startConversation(mSelectedUser); return true; -- 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/eu') 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 2b0954c6..ec4eb7c5 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 a3868a1d..1aa2bade 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 908c29d2..ab4dc059 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/eu') 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 ec4eb7c5..8ac69d37 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 b0727690..1dd21541 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 c147978e..e74cb65c 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 0dc8d521..8b5f97af 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 1aa2bade..e408fa7f 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/eu') 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 8ac69d37..664a248f 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/eu') 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 664a248f..3c9584d3 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/eu') 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 3c9584d3..ef7c523d 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/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index ec50ea54..026c74ad 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/eu') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index e408fa7f..4fb01942 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/eu') diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 3e443d75..ca62b7db 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/eu') 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 ef7c523d..9c5c82ff 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 06dd2cda..45995228 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 b1dffc82..9695e6fa 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 8b5f97af..9cbe5b46 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 4fb01942..0da52010 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/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 33861f82..4ff47f9e 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/eu') 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 9c5c82ff..6b6c3750 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/eu') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 0da52010..0ba2876a 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/eu') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 0ba2876a..4c6bf221 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/eu') 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 6b6c3750..23adbad8 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/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 4ff47f9e..ebc1ae83 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/eu') diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 9695e6fa..ac6c6625 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 167f3f02..d2c75a5e 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/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index e4ce4a0f..2e50af3b 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 eebeb040..7c994c31 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/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index ab4dc059..b33e909f 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/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index b33e909f..e3a43b8b 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/eu') diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index ac6c6625..698775c8 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/eu') 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 23adbad8..57e57f7f 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 4c6bf221..39ef5d36 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 ca62b7db..92e4cf35 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 ebc1ae83..ef99b491 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 d2c75a5e..df3a391b 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/eu') diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 92e4cf35..60815f5e 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/eu') diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 60815f5e..08c0b3fa 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/eu') 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 57e57f7f..8358125d 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/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index ef99b491..e7a8ffb7 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 e3a43b8b..77ca2a67 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 7c994c31..9dfece2f 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/eu') diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 62fe4191..a9c106ab 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 a3ab8dab..c0d4455a 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/eu') 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 8358125d..b05112a3 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 39ef5d36..a2c62a8c 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 08c0b3fa..cc113cef 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 2e50af3b..a6cd0431 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 026c74ad..15491dea 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 00000000..4efa4f6c --- /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 9dfece2f..00322452 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 00000000..59dc1c1e --- /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/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 77ca2a67..1f5b76a3 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/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 1f5b76a3..2b352f03 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/eu') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index a2c62a8c..8168b1a6 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/eu') diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index cc113cef..14c5c23b 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 9c91b9036aa57c610c26b4cdf52ee295eaa0dc95 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 20 Jul 2015 14:24:59 +0200 Subject: don't show 'show contact details' context menu item when contact isn not in roster --- .../eu/siacs/conversations/ui/ConferenceDetailsActivity.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 614a6648..3d15d3e1 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -283,7 +283,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room); MenuItem banFromConference = menu.findItem(R.id.ban_from_conference); startConversation.setVisible(true); - showContactDetails.setVisible(true); + if (contact != null) { + showContactDetails.setVisible(true); + } if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) && self.getAffiliation().outranks(user.getAffiliation())) { if (mAdvancedMode) { @@ -305,14 +307,17 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } } - super.onCreateContextMenu(menu,v,menuInfo); + super.onCreateContextMenu(menu, v, menuInfo); } @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_contact_details: - switchToContactDetails(mSelectedUser.getContact()); + Contact contact = mSelectedUser.getContact(); + if (contact != null) { + switchToContactDetails(contact); + } return true; case R.id.start_conversation: startConversation(mSelectedUser); -- 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/eu') diff --git a/src/main/java/eu/siacs/conversations/crypto/OtrService.java b/src/main/java/eu/siacs/conversations/crypto/OtrService.java index 1e905bac..f81c9865 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 505c4b0f..8f8122f0 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 b05112a3..4189aba4 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 45995228..1378c94a 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 fdb5f932..f924c05a 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 ae9ba1f1..ca325448 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 d867a370..6ff6b8c9 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 19c1d4f7..88ff8f46 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 1dd21541..06ec0284 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 a9c106ab..2ece1cca 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 c0d4455a..e170590e 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 24e93db1..18331796 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 9cbe5b46..38e0f98a 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 ab191285..d607345e 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 dfbe9db7..ceab1592 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 14c5c23b..93420a24 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 a57e1b89..bd2042fb 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 54c064c6..d40a1121 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 c9e99ce5..b2ea28a0 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 07b8819d..5138047d 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 a6cd0431..dbf4490e 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 15491dea..f40b06c8 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 2b352f03..379d0728 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 56dbc55e..e1189e7a 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 eb5d9b2e..5ce361df 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 351f1dfc..fed82065 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 4efa4f6c..ab32e61a 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 00322452..77001f20 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 226b1920..ece4ac6b 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 bfe44326..6918713e 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 0993735f..471526af 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 7b20b55f..ad7d7622 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 df3a391b..08e0f298 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 c72e760e..fd3b5553 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 5a47bb3c..36d03b30 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 0ad57fe2..4e3ec236 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 ee3ea3e1..0f182847 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 9a689768..f18a4ed8 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 99e8ebb8..b90f06ff 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 2e768ad9..cac23f07 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 52d3d46a..aeaaa593 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 35c89b45..7c81d988 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 65cafe79..138a5e51 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 cadf9df3..ab564480 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 9a02ee7a..6f31f163 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 74da6a9b..38bb5c8f 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 628f0d93..6b690912 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/eu') 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 4189aba4..827ea44d 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 8168b1a6..71b4cba9 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 ab32e61a..e93cacd4 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 From 504ef0b72ebd951e3f3f493435fb96713a2b4efe Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 20 Jul 2015 15:48:58 +0200 Subject: rely on refreshUi/refreshUiReal and make sure it is being used everywhere --- src/main/java/eu/siacs/conversations/Config.java | 1 - .../conversations/http/HttpDownloadConnection.java | 5 +-- .../siacs/conversations/ui/BlocklistActivity.java | 15 +++---- .../conversations/ui/ChangePasswordActivity.java | 4 ++ .../conversations/ui/ChooseContactActivity.java | 4 ++ .../conversations/ui/ContactDetailsActivity.java | 17 +++----- .../conversations/ui/ConversationActivity.java | 7 +-- .../conversations/ui/EditAccountActivity.java | 51 +++++++++++----------- .../ui/PublishProfilePictureActivity.java | 4 ++ .../siacs/conversations/ui/SettingsActivity.java | 4 ++ .../siacs/conversations/ui/ShareWithActivity.java | 4 ++ .../siacs/conversations/ui/TrustKeysActivity.java | 14 ++---- .../eu/siacs/conversations/ui/XmppActivity.java | 6 +-- .../xmpp/jingle/JingleConnection.java | 6 +-- 14 files changed, 66 insertions(+), 76 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index ce91244d..0f4b3404 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -24,7 +24,6 @@ public final class Config { public static final int PAGE_SIZE = 50; public static final int MAX_NUM_PAGES = 3; - public static final int PROGRESS_UI_UPDATE_INTERVAL = 750; public static final int REFRESH_UI_INTERVAL = 500; public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 2ece1cca..d7487af9 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -244,10 +244,7 @@ public class HttpDownloadConnection implements Transferable { public void updateProgress(int i) { this.mProgress = i; - if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) { - this.mLastGuiRefresh = SystemClock.elapsedRealtime(); - mXmppConnectionService.updateConversationUi(); - } + mXmppConnectionService.updateConversationUi(); } @Override diff --git a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java index 13d7f4fc..08594201 100644 --- a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java @@ -55,16 +55,10 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem } Collections.sort(getListItems()); } - runOnUiThread(new Runnable() { - @Override - public void run() { - getListItemAdapter().notifyDataSetChanged(); - } - }); + getListItemAdapter().notifyDataSetChanged(); } - @Override - public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { + protected void refreshUiReal() { final Editable editable = getSearchEditText().getText(); if (editable != null) { filterContacts(editable.toString()); @@ -72,4 +66,9 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem filterContacts(); } } + + @Override + public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { + refreshUi(); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java index d40a1121..aa986bd1 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java @@ -103,4 +103,8 @@ public class ChangePasswordActivity extends XmppActivity implements XmppConnecti }); } + + public void refreshUiReal() { + + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java index b2ea28a0..bc23c3ba 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java @@ -149,4 +149,8 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { return result.toArray(new String[result.size()]); } + + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index e7a8ffb7..cc2ef27c 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -158,6 +158,11 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd refreshUi(); } + @Override + public void OnUpdateBlocklist(final Status status) { + refreshUi(); + } + @Override protected void refreshUiReal() { invalidateOptionsMenu(); @@ -464,16 +469,4 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd populateView(); } } - - @Override - public void OnUpdateBlocklist(final Status status) { - runOnUiThread(new Runnable() { - - @Override - public void run() { - invalidateOptionsMenu(); - populateView(); - } - }); - } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index dbf4490e..7b042be7 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -1290,6 +1290,7 @@ public class ConversationActivity extends XmppActivity ConversationActivity.this.mConversationFragment.updateMessages(); updateActionBarTitle(); } + invalidateOptionsMenu(); } @Override @@ -1310,12 +1311,6 @@ public class ConversationActivity extends XmppActivity @Override public void OnUpdateBlocklist(Status status) { this.refreshUi(); - runOnUiThread(new Runnable() { - @Override - public void run() { - invalidateOptionsMenu(); - } - }); } public void unblockConversation(final Blockable conversation) { diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 379d0728..e7b51329 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -151,34 +151,33 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate finish(); } }; - @Override - public void onAccountUpdate() { - runOnUiThread(new Runnable() { - @Override - public void run() { - invalidateOptionsMenu(); - if (mAccount != null - && mAccount.getStatus() != Account.State.ONLINE - && mFetchingAvatar) { - startActivity(new Intent(getApplicationContext(), - ManageAccountActivity.class)); - finish(); - } else if (jidToEdit == null && mAccount != null - && mAccount.getStatus() == Account.State.ONLINE) { - if (!mFetchingAvatar) { - mFetchingAvatar = true; - xmppConnectionService.checkForAvatar(mAccount, - mAvatarFetchCallback); - } - } else { - updateSaveButton(); - } - if (mAccount != null) { - updateAccountInformation(false); - } + public void refreshUiReal() { + invalidateOptionsMenu(); + if (mAccount != null + && mAccount.getStatus() != Account.State.ONLINE + && mFetchingAvatar) { + startActivity(new Intent(getApplicationContext(), + ManageAccountActivity.class)); + finish(); + } else if (jidToEdit == null && mAccount != null + && mAccount.getStatus() == Account.State.ONLINE) { + if (!mFetchingAvatar) { + mFetchingAvatar = true; + xmppConnectionService.checkForAvatar(mAccount, + mAvatarFetchCallback); } - }); + } else { + updateSaveButton(); + } + if (mAccount != null) { + updateAccountInformation(false); + } + } + + @Override + public void onAccountUpdate() { + refreshUi(); } private final UiCallback mAvatarFetchCallback = new UiCallback() { diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index 4333dbdb..2f6d765d 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -251,4 +251,8 @@ public class PublishProfilePictureActivity extends XmppActivity { this.publishButton.setTextColor(getSecondaryTextColor()); } + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } + } diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java index 5ce361df..6e5fe610 100644 --- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java @@ -180,4 +180,8 @@ public class SettingsActivity extends XmppActivity implements } } + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } + } diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index fed82065..e0ebd5c2 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -228,4 +228,8 @@ public class ShareWithActivity extends XmppActivity { } + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } + } diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index e93cacd4..d88d7902 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -210,16 +210,10 @@ public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailabl @Override public void onNewKeysAvailable() { - runOnUiThread(new Runnable() { - @Override - public void run() { - final Account account = xmppConnectionService - .findAccountByJid(accountJid); - hasPendingFetches = false; - getFingerprints(account); - refreshUi(); - } - }); + final Account account = xmppConnectionService.findAccountByJid(accountJid); + hasPendingFetches = false; + getFingerprints(account); + refreshUi(); } private void commitTrusts() { diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 77001f20..69357224 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -125,7 +125,7 @@ public abstract class XmppActivity extends Activity { protected ConferenceInvite mPendingConferenceInvite = null; - protected void refreshUi() { + protected final void refreshUi() { final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh; if (diff > Config.REFRESH_UI_INTERVAL) { mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable); @@ -137,9 +137,7 @@ public abstract class XmppActivity extends Activity { } } - protected void refreshUiReal() { - - }; + abstract protected void refreshUiReal(); protected interface OnValueEdited { public void onValueEdited(String value); 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 138a5e51..e15fc522 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -60,7 +60,6 @@ public class JingleConnection implements Transferable { private String contentCreator; private int mProgress = 0; - private long mLastGuiRefresh = 0; private boolean receivedCandidate = false; private boolean sentCandidate = false; @@ -902,10 +901,7 @@ public class JingleConnection implements Transferable { public void updateProgress(int i) { this.mProgress = i; - if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) { - this.mLastGuiRefresh = SystemClock.elapsedRealtime(); - mXmppConnectionService.updateConversationUi(); - } + mXmppConnectionService.updateConversationUi(); } interface OnProxyActivated { -- cgit v1.2.3 From e6cb12dfe414497b4317820497985c110cb81864 Mon Sep 17 00:00:00 2001 From: Raphael Pohl Date: Sun, 19 Jul 2015 14:26:03 +0200 Subject: changed message bubble appearance --- .../conversations/ui/adapter/MessageAdapter.java | 37 ++++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) (limited to 'src/main/java/eu') 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 08e0f298..679fb355 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -81,15 +81,31 @@ public class MessageAdapter extends ArrayAdapter { return 3; } - @Override - public int getItemViewType(int position) { - if (getItem(position).getType() == Message.TYPE_STATUS) { + public int getItemViewType(Message message) { + if (message.getType() == Message.TYPE_STATUS) { return STATUS; - } else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) { + } else if (message.getStatus() <= Message.STATUS_RECEIVED) { return RECEIVED; - } else { - return SENT; } + + return SENT; + } + + @Override + public int getItemViewType(int position) { + return this.getItemViewType(getItem(position)); + } + + private int getMessageTextColor(Message message) { + int type = this.getItemViewType(message); + + if (type == SENT) { + return activity.getResources().getColor(R.color.black87); + } else if (type == RECEIVED) { + return activity.getResources().getColor(R.color.white); + } + + return activity.getPrimaryTextColor(); } private void displayStatus(ViewHolder viewHolder, Message message) { @@ -150,7 +166,7 @@ public class MessageAdapter extends ArrayAdapter { if (error) { viewHolder.time.setTextColor(activity.getWarningTextColor()); } else { - viewHolder.time.setTextColor(activity.getSecondaryTextColor()); + viewHolder.time.setTextColor(this.getMessageTextColor(message)); } if (message.getEncryption() == Message.ENCRYPTION_NONE) { viewHolder.indicator.setVisibility(View.GONE); @@ -294,7 +310,7 @@ public class MessageAdapter extends ArrayAdapter { } else { viewHolder.messageBody.setText(""); } - viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor()); + viewHolder.messageBody.setTextColor(this.getMessageTextColor(message)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setTextIsSelectable(true); } @@ -363,8 +379,9 @@ public class MessageAdapter extends ArrayAdapter { scalledW = (int) target; scalledH = (int) (params.height / ((double) params.width / target)); } - viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams( - scalledW, scalledH)); + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scalledW, scalledH); + layoutParams.setMargins(0, (int)(metrics.density * 4), 0, (int)(metrics.density * 4)); + viewHolder.image.setLayoutParams(layoutParams); activity.loadBitmap(message, viewHolder.image); viewHolder.image.setOnClickListener(new OnClickListener() { -- cgit v1.2.3 From e79f82ca72cd8f603a2bda593c6887130a17f4b7 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 20 Jul 2015 18:11:33 +0200 Subject: attempt to fix the delay problem --- .../crypto/axolotl/AxolotlService.java | 10 +++--- .../conversations/generator/MessageGenerator.java | 34 +++++--------------- .../conversations/http/HttpConnectionManager.java | 4 +-- .../conversations/http/HttpUploadConnection.java | 8 +++-- .../services/XmppConnectionService.java | 36 ++++++++++++---------- 5 files changed, 39 insertions(+), 53 deletions(-) (limited to 'src/main/java/eu') 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 827ea44d..1fe455ff 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -892,7 +892,7 @@ public class AxolotlService { new Conversation.OnMessageFound() { @Override public void onMessageFound(Message message) { - processSending(message); + processSending(message,false); } }); } @@ -1053,7 +1053,7 @@ public class AxolotlService { return axolotlMessage; } - private void processSending(final Message message) { + private void processSending(final Message message, final boolean delay) { executor.execute(new Runnable() { @Override public void run() { @@ -1065,17 +1065,17 @@ public class AxolotlService { } else { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Generated message, caching: " + message.getUuid()); messageCache.put(message.getUuid(), packet); - mXmppConnectionService.resendMessage(message); + mXmppConnectionService.resendMessage(message,delay); } } }); } - public void prepareMessage(final Message message) { + public void prepareMessage(final Message message,final boolean delay) { if (!messageCache.containsKey(message.getUuid())) { boolean newSessions = createSessionsIfNeeded(message.getConversation(), true); if (!newSessions) { - this.processSending(message); + this.processSending(message,delay); } } } diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 06ec0284..d3c35789 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -27,7 +27,7 @@ public class MessageGenerator extends AbstractGenerator { super(service); } - private MessagePacket preparePacket(Message message, boolean addDelay) { + private MessagePacket preparePacket(Message message) { Conversation conversation = message.getConversation(); Account account = conversation.getAccount(); MessagePacket packet = new MessagePacket(); @@ -50,13 +50,10 @@ public class MessageGenerator extends AbstractGenerator { } packet.setFrom(account.getJid()); packet.setId(message.getUuid()); - if (addDelay) { - addDelay(packet, message.getTimeSent()); - } return packet; } - private void addDelay(MessagePacket packet, long timestamp) { + public void addDelay(MessagePacket packet, long timestamp) { final SimpleDateFormat mDateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); @@ -66,11 +63,7 @@ public class MessageGenerator extends AbstractGenerator { } public MessagePacket generateAxolotlChat(Message message) { - return generateAxolotlChat(message, false); - } - - public MessagePacket generateAxolotlChat(Message message, boolean addDelay) { - MessagePacket packet = preparePacket(message, addDelay); + MessagePacket packet = preparePacket(message); AxolotlService service = message.getConversation().getAccount().getAxolotlService(); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(message.getConversation().getAccount())+"Submitting message to axolotl service for send processing..."); XmppAxolotlMessage axolotlMessage = service.encrypt(message); @@ -82,15 +75,11 @@ public class MessageGenerator extends AbstractGenerator { } public MessagePacket generateOtrChat(Message message) { - return generateOtrChat(message, false); - } - - public MessagePacket generateOtrChat(Message message, boolean addDelay) { Session otrSession = message.getConversation().getOtrSession(); if (otrSession == null) { return null; } - MessagePacket packet = preparePacket(message, addDelay); + MessagePacket packet = preparePacket(message); packet.addChild("private", "urn:xmpp:carbons:2"); packet.addChild("no-copy", "urn:xmpp:hints"); packet.addChild("no-permanent-store", "urn:xmpp:hints"); @@ -109,11 +98,7 @@ public class MessageGenerator extends AbstractGenerator { } public MessagePacket generateChat(Message message) { - return generateChat(message, false); - } - - public MessagePacket generateChat(Message message, boolean addDelay) { - MessagePacket packet = preparePacket(message, addDelay); + MessagePacket packet = preparePacket(message); if (message.hasFileOnRemoteHost()) { packet.setBody(message.getFileParams().url.toString()); } else { @@ -123,11 +108,7 @@ public class MessageGenerator extends AbstractGenerator { } public MessagePacket generatePgpChat(Message message) { - return generatePgpChat(message, false); - } - - public MessagePacket generatePgpChat(Message message, boolean addDelay) { - MessagePacket packet = preparePacket(message, addDelay); + MessagePacket packet = preparePacket(message); packet.setBody("This is an XEP-0027 encrypted message"); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody()); @@ -151,8 +132,7 @@ public class MessageGenerator extends AbstractGenerator { packet.setType(MessagePacket.TYPE_NORMAL); packet.setTo(to); packet.setFrom(account.getJid()); - Element received = packet.addChild("displayed", - "urn:xmpp:chat-markers:0"); + Element received = packet.addChild("displayed","urn:xmpp:chat-markers:0"); received.setAttribute("id", id); return packet; } diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java index 58a6d1e3..90fbadfe 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java @@ -38,9 +38,9 @@ public class HttpConnectionManager extends AbstractConnectionManager { return connection; } - public HttpUploadConnection createNewUploadConnection(Message message) { + public HttpUploadConnection createNewUploadConnection(Message message, boolean delay) { HttpUploadConnection connection = new HttpUploadConnection(this); - connection.init(message); + connection.init(message,delay); this.uploadConnections.add(connection); return connection; } diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index e170590e..c25bf13a 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -33,6 +33,7 @@ public class HttpUploadConnection implements Transferable { private XmppConnectionService mXmppConnectionService; private boolean canceled = false; + private boolean delayed = false; private Account account; private DownloadableFile file; private Message message; @@ -80,13 +81,14 @@ public class HttpUploadConnection implements Transferable { mXmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED); } - public void init(Message message) { + public void init(Message message, boolean delay) { this.message = message; message.setTransferable(this); mXmppConnectionService.markMessage(message,Message.STATUS_UNSEND); this.account = message.getConversation().getAccount(); this.file = mXmppConnectionService.getFileBackend().getFile(message, false); this.file.setExpectedSize(this.file.getSize()); + this.delayed = delay; if (Config.ENCRYPT_ON_HTTP_UPLOADED || message.getEncryption() == Message.ENCRYPTION_AXOLOTL @@ -172,7 +174,7 @@ public class HttpUploadConnection implements Transferable { mXmppConnectionService.getPgpEngine().encrypt(message, new UiCallback() { @Override public void success(Message message) { - mXmppConnectionService.resendMessage(message); + mXmppConnectionService.resendMessage(message,delayed); } @Override @@ -186,7 +188,7 @@ public class HttpUploadConnection implements Transferable { } }); } else { - mXmppConnectionService.resendMessage(message); + mXmppConnectionService.resendMessage(message,delayed); } } else { fail(); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 93420a24..e43b2536 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -677,22 +677,22 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - private void sendFileMessage(final Message message) { + private void sendFileMessage(final Message message, final boolean delay) { Log.d(Config.LOGTAG, "send file message"); final Account account = message.getConversation().getAccount(); final XmppConnection connection = account.getXmppConnection(); if (connection != null && connection.getFeatures().httpUpload()) { - mHttpConnectionManager.createNewUploadConnection(message); + mHttpConnectionManager.createNewUploadConnection(message, delay); } else { mJingleConnectionManager.createNewConnection(message); } } public void sendMessage(final Message message) { - sendMessage(message, false); + sendMessage(message, false, false); } - private void sendMessage(final Message message, final boolean resend) { + private void sendMessage(final Message message, final boolean resend, final boolean delay) { final Account account = message.getConversation().getAccount(); final Conversation conversation = message.getConversation(); account.deactivateGracePeriod(); @@ -716,24 +716,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa case Message.ENCRYPTION_NONE: if (message.needsUploading()) { if (account.httpUploadAvailable() || message.fixCounterpart()) { - this.sendFileMessage(message); + this.sendFileMessage(message,delay); } else { break; } } else { - packet = mMessageGenerator.generateChat(message,resend); + packet = mMessageGenerator.generateChat(message); } break; case Message.ENCRYPTION_PGP: case Message.ENCRYPTION_DECRYPTED: if (message.needsUploading()) { if (account.httpUploadAvailable() || message.fixCounterpart()) { - this.sendFileMessage(message); + this.sendFileMessage(message,delay); } else { break; } } else { - packet = mMessageGenerator.generatePgpChat(message,resend); + packet = mMessageGenerator.generatePgpChat(message); } break; case Message.ENCRYPTION_OTR: @@ -747,7 +747,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (message.needsUploading()) { mJingleConnectionManager.createNewConnection(message); } else { - packet = mMessageGenerator.generateOtrChat(message,resend); + packet = mMessageGenerator.generateOtrChat(message); } } else if (otrSession == null) { if (message.fixCounterpart()) { @@ -760,14 +760,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa case Message.ENCRYPTION_AXOLOTL: if (message.needsUploading()) { if (account.httpUploadAvailable() || message.fixCounterpart()) { - this.sendFileMessage(message); + this.sendFileMessage(message,delay); } else { break; } } else { packet = account.getAxolotlService().fetchPacketFromCache(message); if (packet == null) { - account.getAxolotlService().prepareMessage(message); + account.getAxolotlService().prepareMessage(message,delay); message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", "")); } } @@ -822,6 +822,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa updateConversationUi(); } if (packet != null) { + if (delay) { + mMessageGenerator.addDelay(packet,message.getTimeSent()); + } if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { if (this.sendChatStates()) { packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); @@ -836,13 +839,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onMessageFound(Message message) { - resendMessage(message); + resendMessage(message,true); } }); } - public void resendMessage(final Message message) { - sendMessage(message, true); + public void resendMessage(final Message message, final boolean delay) { + sendMessage(message, true, delay); } public void fetchRosterFromServer(final Account account) { @@ -1846,8 +1849,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (message.needsUploading()) { mJingleConnectionManager.createNewConnection(message); } else { - MessagePacket outPacket = mMessageGenerator.generateOtrChat(message, true); + MessagePacket outPacket = mMessageGenerator.generateOtrChat(message); if (outPacket != null) { + mMessageGenerator.addDelay(outPacket,message.getTimeSent()); message.setStatus(Message.STATUS_SEND); databaseBackend.updateMessage(message); sendMessagePacket(account, outPacket); @@ -2526,7 +2530,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } for (final Message msg : messages) { markMessage(msg, Message.STATUS_WAITING); - this.resendMessage(msg); + this.resendMessage(msg,true); } } -- cgit v1.2.3 From 0ee64124fe317f6959d1ff61cc9e70b272bf8eb5 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 20 Jul 2015 22:02:54 +0200 Subject: Fix getNumTrustedKeys --- .../java/eu/siacs/conversations/persistance/DatabaseBackend.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 71b4cba9..6091b352 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -863,11 +863,13 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteDatabase db = getReadableDatabase(); String[] args = { account.getUuid(), - name + name, + String.valueOf(AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED.ordinal()) }; return DatabaseUtils.queryNumEntries(db, AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?" - + " AND " + AxolotlService.SQLiteAxolotlStore.NAME + " = ?", + + " AND " + AxolotlService.SQLiteAxolotlStore.NAME + " = ?" + + " AND " + AxolotlService.SQLiteAxolotlStore.TRUSTED + " = ?", args ); } -- cgit v1.2.3 From ab2d114bbc21a5c2d684f8760cb8e4cea54be5de Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 20 Jul 2015 22:18:24 +0200 Subject: Add purge axolotl key option Can now long-press a key to permanently purge it. --- .../crypto/axolotl/AxolotlService.java | 84 ++++++++++++++-------- .../crypto/axolotl/XmppAxolotlMessage.java | 7 +- .../conversations/ui/ContactDetailsActivity.java | 3 +- .../conversations/ui/EditAccountActivity.java | 3 +- .../eu/siacs/conversations/ui/XmppActivity.java | 37 ++++++++-- 5 files changed, 96 insertions(+), 38 deletions(-) (limited to 'src/main/java/eu') 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 1fe455ff..6e28f111 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -100,7 +100,8 @@ public class AxolotlService { public enum Trust { UNDECIDED, // 0 TRUSTED, - UNTRUSTED; + UNTRUSTED, + COMPROMISED; public String toString() { switch(this){ @@ -514,41 +515,64 @@ public class AxolotlService { return fingerprint; } + private SQLiteAxolotlStore.Trust getTrust() { + return sqLiteAxolotlStore.getFingerprintTrust(fingerprint); + } + + @Nullable 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()); - 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(); + switch (getTrust()) { + case UNDECIDED: + case TRUSTED: + try { + try { + PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); + 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"); + WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); + plaintext = cipher.decrypt(message); + } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"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()); } - } 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.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"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()); + + break; + + case COMPROMISED: + case UNTRUSTED: + default: + // ignore + break; } 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; + @Nullable + public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(@NonNull byte[] outgoingMessage) { + SQLiteAxolotlStore.Trust trust = getTrust(); + if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { + CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); + XmppAxolotlMessage.XmppAxolotlMessageHeader header = + new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), + ciphertextMessage.serialize()); + return header; + } else { + return null; + } } } @@ -742,6 +766,10 @@ public class AxolotlService { }); } + public void purgeKey(IdentityKey identityKey) { + axolotlStore.setFingerprintTrust(identityKey.getFingerprint().replaceAll("\\s",""), SQLiteAxolotlStore.Trust.COMPROMISED); + } + 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/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index 1378c94a..ec068ec7 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.crypto.axolotl; +import android.support.annotation.Nullable; import android.util.Base64; import java.security.InvalidAlgorithmParameterException; @@ -145,8 +146,10 @@ public class XmppAxolotlMessage { return headers; } - public void addHeader(XmppAxolotlMessageHeader header) { - headers.add(header); + public void addHeader(@Nullable XmppAxolotlMessageHeader header) { + if (header != null) { + headers.add(header); + } } public byte[] getInnerKey(){ diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index cc2ef27c..16e16cff 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -384,8 +384,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys( contact.getAccount(), contact.getJid().toBareJid().toString())) { - hasKeys = true; - addFingerprintRow(keys, contact.getAccount(), identityKey); + hasKeys |= 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 e7b51329..c9e70082 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -566,8 +566,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate if(ownKey.equals(identityKey)) { continue; } - hasKeys = true; - addFingerprintRow(keys, mAccount, identityKey); + hasKeys |= addFingerprintRow(keys, mAccount, identityKey); } if (hasKeys) { keysCard.setVisibility(View.VISIBLE); diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 69357224..19783627 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -601,11 +601,11 @@ public abstract class XmppActivity extends Activity { builder.create().show(); } - protected void addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) { + protected boolean 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, + return addFingerprintRowWithListeners(keys, account, identityKey, trust, true, new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @@ -633,13 +633,16 @@ public abstract class XmppActivity extends Activity { ); } - protected void addFingerprintRowWithListeners(LinearLayout keys, final Account account, - IdentityKey identityKey, + protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account, + final IdentityKey identityKey, AxolotlService.SQLiteAxolotlStore.Trust trust, boolean showTag, CompoundButton.OnCheckedChangeListener onCheckedChangeListener, View.OnClickListener onClickListener) { + if (trust == AxolotlService.SQLiteAxolotlStore.Trust.COMPROMISED) { + return false; + } 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); @@ -647,6 +650,13 @@ public abstract class XmppActivity extends Activity { trustToggle.setVisibility(View.VISIBLE); trustToggle.setOnCheckedChangeListener(onCheckedChangeListener); trustToggle.setOnClickListener(onClickListener); + view.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + showPurgeKeyDialog(account, identityKey); + return true; + } + }); switch (trust) { case UNTRUSTED: @@ -668,7 +678,26 @@ public abstract class XmppActivity extends Activity { key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint())); keys.addView(view); + return true; + } + public void showPurgeKeyDialog(final Account account, final IdentityKey identityKey) { + Builder builder = new Builder(this); + builder.setTitle(getString(R.string.purge_key)); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage(getString(R.string.purge_key_desc_part1) + + "\n\n" + CryptoHelper.prettifyFingerprint(identityKey.getFingerprint()) + + "\n\n" + getString(R.string.purge_key_desc_part2)); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.accept), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + account.getAxolotlService().purgeKey(identityKey); + refreshUi(); + } + }); + builder.create().show(); } public void selectPresence(final Conversation conversation, -- cgit v1.2.3 From 4ee3f330f51383a435bf2033d8381b990a05dc60 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 20 Jul 2015 22:19:04 +0200 Subject: Do not display clear devices button if not online --- src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java | 1 + 1 file changed, 1 insertion(+) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index c9e70082..ac0f6616 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -372,6 +372,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate showBlocklist.setVisible(false); showMoreInfo.setVisible(false); changePassword.setVisible(false); + clearDevices.setVisible(false); } return true; } -- cgit v1.2.3 From 8be0e8a27ddd4d55a48a38efc5434a581be6f1b3 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 20 Jul 2015 22:35:07 +0200 Subject: Start TrustKeysActivity if no keys are TRUSTED If there are no UNDECIDED keys, but none of the contact's keys are trusted, redirect the user to the TrustKeysActivity --- .../siacs/conversations/crypto/axolotl/AxolotlService.java | 12 ++++++------ .../java/eu/siacs/conversations/ui/ConversationActivity.java | 9 +++++++-- .../java/eu/siacs/conversations/ui/TrustKeysActivity.java | 10 ++++++++-- 3 files changed, 21 insertions(+), 10 deletions(-) (limited to 'src/main/java/eu') 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 6e28f111..72d1d14a 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -278,8 +278,8 @@ public class AxolotlService { mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust); } - public Set getContactUndecidedKeys(String bareJid) { - return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, Trust.UNDECIDED); + public Set getContactUndecidedKeys(String bareJid, Trust trust) { + return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust); } public long getContactNumTrustedKeys(String bareJid) { @@ -692,12 +692,12 @@ public class AxolotlService { return axolotlStore.getIdentityKeyPair().getPublicKey(); } - public Set getPendingKeys() { - return axolotlStore.getContactUndecidedKeys(account.getJid().toBareJid().toString()); + public Set getKeysWithTrust(SQLiteAxolotlStore.Trust trust) { + return axolotlStore.getContactUndecidedKeys(account.getJid().toBareJid().toString(), trust); } - public Set getPendingKeys(Contact contact) { - return axolotlStore.getContactUndecidedKeys(contact.getJid().toBareJid().toString()); + public Set getKeysWithTrust(SQLiteAxolotlStore.Trust trust, Contact contact) { + return axolotlStore.getContactUndecidedKeys(contact.getJid().toBareJid().toString(), trust); } public long getNumTrustedKeys(Contact contact) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 7b042be7..1a643b7e 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -38,6 +38,7 @@ import de.timroes.android.listview.EnhancedListView; 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.Blockable; import eu.siacs.conversations.entities.Contact; @@ -1255,13 +1256,17 @@ public class ConversationActivity extends XmppActivity protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) { AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService(); - if(!axolotlService.getPendingKeys(mSelectedConversation.getContact()).isEmpty() - || !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty()) { + boolean hasPendingKeys = !axolotlService.getKeysWithTrust(Trust.UNDECIDED, + mSelectedConversation.getContact()).isEmpty() + || !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty(); + boolean hasNoTrustedKeys = axolotlService.getNumTrustedKeys(mSelectedConversation.getContact()) == 0; + if( hasPendingKeys || hasNoTrustedKeys) { 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); + intent.putExtra("has_no_trusted", hasNoTrustedKeys); startActivityForResult(intent, requestCode); return true; } else { diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index d88d7902..ccdef9c3 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -29,6 +29,7 @@ public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailabl private Jid contactJid; private boolean hasOtherTrustedKeys = false; private boolean hasPendingFetches = false; + private boolean hasNoTrustedKeys = true; private Contact contact; private TextView ownKeysTitle; @@ -89,6 +90,7 @@ public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailabl this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact")); } catch (final InvalidJidException ignored) { } + hasNoTrustedKeys = getIntent().getBooleanExtra("has_no_trusted", false); ownKeysTitle = (TextView) findViewById(R.id.own_keys_title); ownKeys = (LinearLayout) findViewById(R.id.own_keys_details); @@ -169,13 +171,17 @@ public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailabl } private void getFingerprints(final Account account) { - Set ownKeysSet = account.getAxolotlService().getPendingKeys(); + Set ownKeysSet = account.getAxolotlService().getKeysWithTrust(Trust.UNDECIDED); + Set foreignKeysSet = account.getAxolotlService().getKeysWithTrust(Trust.UNDECIDED, contact); + if (hasNoTrustedKeys) { + ownKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(Trust.UNTRUSTED)); + foreignKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(Trust.UNTRUSTED, contact)); + } 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); -- cgit v1.2.3 From d2845e9ac1040a269401ef9d59e27bac9e7b7d5a Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 20 Jul 2015 23:13:28 +0200 Subject: Refactor axolotl send processing/caching flow --- .../conversations/crypto/axolotl/AxolotlService.java | 17 ++++++++--------- .../siacs/conversations/generator/MessageGenerator.java | 5 +---- .../conversations/services/XmppConnectionService.java | 11 +++++++---- 3 files changed, 16 insertions(+), 17 deletions(-) (limited to 'src/main/java/eu') 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 72d1d14a..882ead3c 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -68,7 +68,7 @@ public class AxolotlService { private final SQLiteAxolotlStore axolotlStore; private final SessionMap sessions; private final Map> deviceIds; - private final Map messageCache; + private final Map messageCache; private final FetchStatusMap fetchStatusMap; private final SerialSingleThreadExecutor executor; @@ -1085,14 +1085,13 @@ public class AxolotlService { executor.execute(new Runnable() { @Override public void run() { - MessagePacket packet = mXmppConnectionService.getMessageGenerator() - .generateAxolotlChat(message); - if (packet == null) { + XmppAxolotlMessage axolotlMessage = encrypt(message); + if (axolotlMessage == null) { mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); //mXmppConnectionService.updateConversationUi(); } else { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Generated message, caching: " + message.getUuid()); - messageCache.put(message.getUuid(), packet); + messageCache.put(message.getUuid(), axolotlMessage); mXmppConnectionService.resendMessage(message,delay); } } @@ -1108,15 +1107,15 @@ public class AxolotlService { } } - public MessagePacket fetchPacketFromCache(Message message) { - MessagePacket packet = messageCache.get(message.getUuid()); - if (packet != null) { + public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) { + XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid()); + if (axolotlMessage != null) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Cache hit: " + message.getUuid()); messageCache.remove(message.getUuid()); } else { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Cache miss: " + message.getUuid()); } - return packet; + return axolotlMessage; } public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) { diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index d3c35789..fed92a27 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -62,11 +62,8 @@ public class MessageGenerator extends AbstractGenerator { delay.setAttribute("stamp", mDateFormat.format(date)); } - public MessagePacket generateAxolotlChat(Message message) { + public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) { MessagePacket packet = preparePacket(message); - AxolotlService service = message.getConversation().getAccount().getAxolotlService(); - 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/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index e43b2536..e7df9e6a 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.XmppAxolotlMessage; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Bookmark; @@ -765,10 +766,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa break; } } else { - packet = account.getAxolotlService().fetchPacketFromCache(message); - if (packet == null) { + XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message); + if (axolotlMessage == null) { account.getAxolotlService().prepareMessage(message,delay); message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", "")); + } else { + packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage); } } break; @@ -839,7 +842,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onMessageFound(Message message) { - resendMessage(message,true); + resendMessage(message, true); } }); } @@ -1851,7 +1854,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } else { MessagePacket outPacket = mMessageGenerator.generateOtrChat(message); if (outPacket != null) { - mMessageGenerator.addDelay(outPacket,message.getTimeSent()); + mMessageGenerator.addDelay(outPacket, message.getTimeSent()); message.setStatus(Message.STATUS_SEND); databaseBackend.updateMessage(message); sendMessagePacket(account, outPacket); -- cgit v1.2.3 From 971aa3a11e1077a38746cb45e7177851725be47e Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 20 Jul 2015 23:16:06 +0200 Subject: Also decrypt messages from UNTRUSTED sessions --- src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/java/eu') 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 882ead3c..b0724593 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -524,6 +524,7 @@ public class AxolotlService { byte[] plaintext = null; switch (getTrust()) { case UNDECIDED: + case UNTRUSTED: case TRUSTED: try { try { @@ -553,7 +554,6 @@ public class AxolotlService { break; case COMPROMISED: - case UNTRUSTED: default: // ignore break; -- cgit v1.2.3 From 122bc97ce24181ccd07cf9badf8d4c3b81d80c3f Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Tue, 21 Jul 2015 01:15:32 +0200 Subject: Switch payload encryption to AES-GCM This also ensures that the IV is generated with proper randomness. --- .../crypto/axolotl/AxolotlService.java | 28 +++++++++++++++++----- .../crypto/axolotl/CryptoFailedException.java | 7 ++++++ .../crypto/axolotl/XmppAxolotlMessage.java | 28 +++++++++++++--------- 3 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java (limited to 'src/main/java/eu') 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 b0724593..fbea3b0f 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -4,6 +4,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.whispersystems.libaxolotl.AxolotlAddress; import org.whispersystems.libaxolotl.DuplicateMessageException; import org.whispersystems.libaxolotl.IdentityKey; @@ -30,6 +31,7 @@ import org.whispersystems.libaxolotl.state.SessionRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.util.KeyHelper; +import java.security.Security; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -678,6 +680,9 @@ public class AxolotlService { } public AxolotlService(Account account, XmppConnectionService connectionService) { + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } this.mXmppConnectionService = connectionService; this.account = account; this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); @@ -1050,11 +1055,17 @@ public class AxolotlService { 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(), content); + } else { + content = message.getBody(); + } + final XmppAxolotlMessage axolotlMessage; + try { + axolotlMessage = new XmppAxolotlMessage(message.getContact().getJid().toBareJid(), + getOwnDeviceId(), content); + } catch (CryptoFailedException e) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage()); + return null; + } if(findSessionsforContact(message.getContact()).isEmpty()) { return null; @@ -1143,7 +1154,12 @@ 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, session.getFingerprint()); + try{ + plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint()); + } catch (CryptoFailedException e) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage()); + break; + } } Integer preKeyId = session.getPreKeyId(); if (preKeyId != null) { diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java new file mode 100644 index 00000000..5796ef30 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java @@ -0,0 +1,7 @@ +package eu.siacs.conversations.crypto.axolotl; + +public class CryptoFailedException extends Exception { + public CryptoFailedException(Exception e){ + super(e); + } +} 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 ec068ec7..24afeaea 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -6,6 +6,8 @@ import android.util.Base64; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; import java.util.HashSet; import java.util.Set; @@ -107,26 +109,30 @@ public class XmppAxolotlMessage { } } - public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) { + public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException{ this.from = from; this.sourceDeviceId = sourceDeviceId; this.headers = new HashSet<>(); this.encrypt(plaintext); } - private void encrypt(String plaintext) { + private void encrypt(String plaintext) throws CryptoFailedException { 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); + SecureRandom random = new SecureRandom(); + this.iv = new byte[16]; + random.nextBytes(iv); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); this.innerKey = secretKey.getEncoded(); - this.iv = cipher.getIV(); this.ciphertext = cipher.doFinal(plaintext.getBytes()); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | IllegalBlockSizeException | BadPaddingException e) { - + | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException + | InvalidAlgorithmParameterException e) { + throw new CryptoFailedException(e); } } @@ -174,11 +180,11 @@ public class XmppAxolotlMessage { } - public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) { + public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) throws CryptoFailedException { XmppAxolotlPlaintextMessage plaintextMessage = null; try { - Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC"); SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); IvParameterSpec ivSpec = new IvParameterSpec(iv); @@ -189,8 +195,8 @@ public class XmppAxolotlMessage { } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException - | BadPaddingException e) { - throw new AssertionError(e); + | BadPaddingException | NoSuchProviderException e) { + throw new CryptoFailedException(e); } return plaintextMessage; } -- cgit v1.2.3 From 639ebd644ba7f6e05bfcb09ad1f616d0f433b5a6 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Tue, 21 Jul 2015 01:17:29 +0200 Subject: Remove unused import --- src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 1 - 1 file changed, 1 deletion(-) (limited to 'src/main/java/eu') 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 fbea3b0f..7549e439 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -53,7 +53,6 @@ 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 { -- cgit v1.2.3 From b7ff2c34614a92f3893732338f75cb0f7fe77d32 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Tue, 21 Jul 2015 01:52:22 +0200 Subject: Use properly fixed numeral values in Trust enum Why, oh God, why?! #thanksjamesgosling --- .../crypto/axolotl/AxolotlService.java | 30 +++++++++++++++++++--- .../conversations/persistance/DatabaseBackend.java | 10 ++++---- 2 files changed, 31 insertions(+), 9 deletions(-) (limited to 'src/main/java/eu') 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 7549e439..fc1e13fd 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -99,10 +99,28 @@ public class AxolotlService { private int currentPreKeyId = 0; public enum Trust { - UNDECIDED, // 0 - TRUSTED, - UNTRUSTED, - COMPROMISED; + UNDECIDED(0), + TRUSTED(1), + UNTRUSTED(2), + COMPROMISED(3); + + private static final Map trustsByValue = new HashMap<>(); + + static { + for (Trust trust : Trust.values()) { + trustsByValue.put(trust.getCode(), trust); + } + } + + private final int code; + + Trust(int code){ + this.code = code; + } + + public int getCode() { + return this.code; + } public String toString() { switch(this){ @@ -119,6 +137,10 @@ public class AxolotlService { public static Trust fromBoolean(Boolean trusted) { return trusted?TRUSTED:UNTRUSTED; } + + public static Trust fromCode(int code) { + return trustsByValue.get(code); + } }; private static IdentityKeyPair generateIdentityKeyPair() { diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 6091b352..3120c008 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -845,7 +845,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { while(cursor.moveToNext()) { if ( trust != null && cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED)) - != trust.ordinal()) { + != trust.getCode()) { continue; } try { @@ -864,7 +864,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] args = { account.getUuid(), name, - String.valueOf(AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED.ordinal()) + String.valueOf(AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED.getCode()) }; return DatabaseUtils.queryNumEntries(db, AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?" @@ -886,7 +886,7 @@ 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()); + values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trusted.getCode()); db.insert(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); } @@ -896,7 +896,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (cursor.getCount() > 0) { cursor.moveToFirst(); int trustValue = cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED)); - trust = AxolotlService.SQLiteAxolotlStore.Trust.values()[trustValue]; + trust = AxolotlService.SQLiteAxolotlStore.Trust.fromCode(trustValue); } cursor.close(); return trust; @@ -909,7 +909,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { fingerprint }; ContentValues values = new ContentValues(); - values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trust.ordinal()); + values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trust.getCode()); int rows = db.update(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ", -- cgit v1.2.3 From f74ee765a2cb54b163140ca482c710af0f68b101 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 Jul 2015 11:46:51 +0200 Subject: bugfix: changed condition of onOtrSessionEstablished being called fixed #1263 fixed #1260 fixed #1293 --- .../java/eu/siacs/conversations/parser/MessageParser.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 38e0f98a..0d0dce49 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -70,18 +70,18 @@ public class MessageParser extends AbstractParser implements try { conversation.setLastReceivedOtrMessageId(id); Session otrSession = conversation.getOtrSession(); - SessionStatus before = otrSession.getSessionStatus(); body = otrSession.transformReceiving(body); - SessionStatus after = otrSession.getSessionStatus(); - if ((before != after) && (after == SessionStatus.ENCRYPTED)) { + SessionStatus status = otrSession.getSessionStatus(); + if (body == null && status == SessionStatus.ENCRYPTED) { conversation.setNextEncryption(Message.ENCRYPTION_OTR); mXmppConnectionService.onOtrSessionEstablished(conversation); - } else if ((before != after) && (after == SessionStatus.FINISHED)) { + return null; + } else if (body == null && status == SessionStatus.FINISHED) { conversation.setNextEncryption(Message.ENCRYPTION_NONE); conversation.resetOtrSession(); mXmppConnectionService.updateConversationUi(); - } - if ((body == null) || (body.isEmpty())) { + return null; + } else if (body == null || (body.isEmpty())) { return null; } if (body.startsWith(CryptoHelper.FILETRANSFER)) { -- cgit v1.2.3 From a5027104fdc3e64bf40a5764a2cf2bbd27a4ba99 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 Jul 2015 11:50:21 +0200 Subject: bugfix: also add no-permanent-storage to message hints --- src/main/java/eu/siacs/conversations/crypto/OtrService.java | 2 +- src/main/java/eu/siacs/conversations/generator/MessageGenerator.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/crypto/OtrService.java b/src/main/java/eu/siacs/conversations/crypto/OtrService.java index f81c9865..45bdeb2e 100644 --- a/src/main/java/eu/siacs/conversations/crypto/OtrService.java +++ b/src/main/java/eu/siacs/conversations/crypto/OtrService.java @@ -182,7 +182,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost { packet.addChild("private", "urn:xmpp:carbons:2"); packet.addChild("no-copy", "urn:xmpp:hints"); packet.addChild("no-permanent-store", "urn:xmpp:hints"); - + packet.addChild("no-permanent-store", "urn:xmpp:hints"); try { Jid jid = Jid.fromSessionID(session); Conversation conversation = mXmppConnectionService.find(account,jid); diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index fed92a27..040bc48b 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -80,6 +80,7 @@ public class MessageGenerator extends AbstractGenerator { packet.addChild("private", "urn:xmpp:carbons:2"); packet.addChild("no-copy", "urn:xmpp:hints"); packet.addChild("no-permanent-store", "urn:xmpp:hints"); + packet.addChild("no-permanent-storage", "urn:xmpp:hints"); try { String content; if (message.hasFileOnRemoteHost()) { -- cgit v1.2.3 From 87bc9d3a31a7f92b13293669c4c5ce2c5a02f6ae Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 Jul 2015 11:52:49 +0200 Subject: end otr session when receiving failed otr message warnings --- src/main/java/eu/siacs/conversations/parser/MessageParser.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 0d0dce49..b81a62da 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -202,7 +202,13 @@ public class MessageParser extends AbstractParser implements if (packet.getType() == MessagePacket.TYPE_ERROR) { Jid from = packet.getFrom(); if (from != null) { - mXmppConnectionService.markMessage(account, from.toBareJid(), packet.getId(), Message.STATUS_SEND_FAILED); + Message message = mXmppConnectionService.markMessage(account, + from.toBareJid(), + packet.getId(), + Message.STATUS_SEND_FAILED); + if (message.getEncryption() == Message.ENCRYPTION_OTR) { + message.getConversation().endOtrIfNeeded(); + } } return true; } -- cgit v1.2.3 From ffa588ba3e40041b44e2ac18efc812df0a11b63d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 Jul 2015 12:01:20 +0200 Subject: and now do that properly --- src/main/java/eu/siacs/conversations/crypto/OtrService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/crypto/OtrService.java b/src/main/java/eu/siacs/conversations/crypto/OtrService.java index 45bdeb2e..1b296cc4 100644 --- a/src/main/java/eu/siacs/conversations/crypto/OtrService.java +++ b/src/main/java/eu/siacs/conversations/crypto/OtrService.java @@ -182,7 +182,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost { packet.addChild("private", "urn:xmpp:carbons:2"); packet.addChild("no-copy", "urn:xmpp:hints"); packet.addChild("no-permanent-store", "urn:xmpp:hints"); - packet.addChild("no-permanent-store", "urn:xmpp:hints"); + packet.addChild("no-permanent-storage", "urn:xmpp:hints"); try { Jid jid = Jid.fromSessionID(session); Conversation conversation = mXmppConnectionService.find(account,jid); -- cgit v1.2.3 From 1aeb5874b042b7461e82dddbb34f70c5149a789f Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 Jul 2015 13:15:59 +0200 Subject: allow for private messages being started from muc details in anonymous mucs --- .../ui/ConferenceDetailsActivity.java | 22 +++++++++++++++------- .../conversations/ui/ConversationActivity.java | 22 ++++++++++++++++++---- .../eu/siacs/conversations/ui/XmppActivity.java | 13 +++++++++---- 3 files changed, 42 insertions(+), 15 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 475bc423..94777931 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -265,14 +265,16 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers final User self = mConversation.getMucOptions().getSelf(); this.mSelectedUser = user; String name; + final Contact contact = user.getContact(); + if (contact != null) { + name = contact.getDisplayName(); + } else if (user.getJid() != null){ + name = user.getJid().toBareJid().toString(); + } else { + name = user.getName(); + } + menu.setHeaderTitle(name); if (user.getJid() != null) { - final Contact contact = user.getContact(); - if (contact != null) { - name = contact.getDisplayName(); - } else { - name = user.getJid().toBareJid().toString(); - } - menu.setHeaderTitle(name); MenuItem showContactDetails = menu.findItem(R.id.action_contact_details); MenuItem startConversation = menu.findItem(R.id.start_conversation); MenuItem giveMembership = menu.findItem(R.id.give_membership); @@ -303,6 +305,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers removeAdminPrivileges.setVisible(true); } } + } else { + MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message); + sendPrivateMessage.setVisible(true); } } @@ -340,6 +345,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.OUTCAST,this); xmppConnectionService.changeRoleInConference(mConversation,mSelectedUser.getName(), MucOptions.Role.NONE,this); return true; + case R.id.send_private_message: + privateMsgInMuc(mConversation,mSelectedUser.getName()); + return true; default: return super.onContextItemSelected(item); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 1a643b7e..92b8e544 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -51,6 +51,8 @@ import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.ui.adapter.ConversationAdapter; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; public class ConversationActivity extends XmppActivity implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast { @@ -62,6 +64,7 @@ public class ConversationActivity extends XmppActivity public static final String MESSAGE = "messageUuid"; public static final String TEXT = "text"; public static final String NICK = "nick"; + public static final String PRIVATE_MESSAGE = "pm"; public static final int REQUEST_SEND_MESSAGE = 0x0201; public static final int REQUEST_DECRYPT_PGP = 0x0202; @@ -464,7 +467,7 @@ public class ConversationActivity extends XmppActivity conversation.setNextCounterpart(null); callback.onPresenceSelected(); } else { - selectPresence(conversation,callback); + selectPresence(conversation, callback); } } @@ -474,7 +477,7 @@ public class ConversationActivity extends XmppActivity if (intent.resolveActivity(getPackageManager()) != null) { return intent; } else { - intent.setData(Uri.parse("http://play.google.com/store/apps/details?id="+packageId)); + intent.setData(Uri.parse("http://play.google.com/store/apps/details?id=" + packageId)); return intent; } } @@ -831,7 +834,7 @@ public class ConversationActivity extends XmppActivity } conversation.setMutedTill(till); ConversationActivity.this.xmppConnectionService.databaseBackend - .updateConversation(conversation); + .updateConversation(conversation); updateConversationList(); ConversationActivity.this.mConversationFragment.updateMessages(); invalidateOptionsMenu(); @@ -995,10 +998,21 @@ public class ConversationActivity extends XmppActivity final String downloadUuid = intent.getStringExtra(MESSAGE); final String text = intent.getStringExtra(TEXT); final String nick = intent.getStringExtra(NICK); + final boolean pm = intent.getBooleanExtra(PRIVATE_MESSAGE,false); if (selectConversationByUuid(uuid)) { this.mConversationFragment.reInit(getSelectedConversation()); if (nick != null) { - this.mConversationFragment.highlightInConference(nick); + if (pm) { + Jid jid = getSelectedConversation().getJid(); + try { + Jid next = Jid.fromParts(jid.getLocalpart(),jid.getDomainpart(),nick); + this.mConversationFragment.privateMessageWith(next); + } catch (final InvalidJidException ignored) { + //do nothing + } + } else { + this.mConversationFragment.highlightInConference(nick); + } } else { this.mConversationFragment.appendText(text); } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 19783627..a0a8d520 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -384,14 +384,18 @@ public abstract class XmppActivity extends Activity { public void switchToConversation(Conversation conversation, String text, boolean newTask) { - switchToConversation(conversation,text,null,newTask); + switchToConversation(conversation,text,null,false,newTask); } public void highlightInMuc(Conversation conversation, String nick) { - switchToConversation(conversation, null, nick, false); + switchToConversation(conversation, null, nick, false, false); } - private void switchToConversation(Conversation conversation, String text, String nick, boolean newTask) { + public void privateMsgInMuc(Conversation conversation, String nick) { + switchToConversation(conversation, null, nick, true, false); + } + + private void switchToConversation(Conversation conversation, String text, String nick, boolean pm, boolean newTask) { Intent viewConversationIntent = new Intent(this, ConversationActivity.class); viewConversationIntent.setAction(Intent.ACTION_VIEW); @@ -402,6 +406,7 @@ public abstract class XmppActivity extends Activity { } if (nick != null) { viewConversationIntent.putExtra(ConversationActivity.NICK, nick); + viewConversationIntent.putExtra(ConversationActivity.PRIVATE_MESSAGE,pm); } viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION); if (newTask) { @@ -456,7 +461,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); -- cgit v1.2.3 From 3c5c0c7d3b4ceaaca51b64ca91d2d1fcd76f9a66 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Tue, 21 Jul 2015 13:51:15 +0200 Subject: Fill own device sessions into SessionMap --- .../crypto/axolotl/AxolotlService.java | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'src/main/java/eu') 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 fc1e13fd..a004599d 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -662,21 +662,28 @@ public class AxolotlService { this.fillMap(store); } + private void putDevicesForJid(String bareJid, List deviceIds, SQLiteAxolotlStore store) { + for (Integer deviceId : deviceIds) { + AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId); + 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)); + } + } + private void fillMap(SQLiteAxolotlStore store) { + List deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toString()); + putDevicesForJid(account.getJid().toBareJid().toString(), deviceIds, store); 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); - 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)); - } + deviceIds = store.getSubDeviceSessions(address); + putDevicesForJid(address, deviceIds, store); } + } @Override -- cgit v1.2.3 From 92b5081b5ebac1a3108a821a270beb3f7d9c39ee Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Tue, 21 Jul 2015 14:18:16 +0200 Subject: Add INACTIVE state for removed keys We introduce a new trust state: INACTIVE. This state is intended for old keys that have been removed. When a TRUSTED device is removed from the PEP devicelist, it's status will be set to INACTIVE. INACTIVE keys are shown in the UI as greyed out, non-interactible key rows. Messages are not encrypted for INACTIVE devices. When an INACTIVE device reappears in PEP, or a message is received from an INACTIVE device, it is set back to trusted. --- .../crypto/axolotl/AxolotlService.java | 54 +++++++++++++++++----- .../services/XmppConnectionService.java | 31 +++++++------ .../conversations/ui/ContactDetailsActivity.java | 8 +++- .../conversations/ui/EditAccountActivity.java | 8 +++- .../siacs/conversations/ui/TrustKeysActivity.java | 6 +-- .../eu/siacs/conversations/ui/XmppActivity.java | 24 ++++++++-- .../conversations/xmpp/OnKeyStatusUpdated.java | 5 ++ .../conversations/xmpp/OnNewKeysAvailable.java | 5 -- 8 files changed, 101 insertions(+), 40 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/xmpp/OnKeyStatusUpdated.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/OnNewKeysAvailable.java (limited to 'src/main/java/eu') 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 a004599d..c30a7ab8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -102,7 +102,8 @@ public class AxolotlService { UNDECIDED(0), TRUSTED(1), UNTRUSTED(2), - COMPROMISED(3); + COMPROMISED(3), + INACTIVE(4); private static final Map trustsByValue = new HashMap<>(); @@ -125,12 +126,16 @@ public class AxolotlService { public String toString() { switch(this){ case UNDECIDED: - return "Trust undecided"; + return "Trust undecided "+getCode(); case TRUSTED: - return "Trusted"; + return "Trusted "+getCode(); + case COMPROMISED: + return "Compromised "+getCode(); + case INACTIVE: + return "Inactive "+getCode(); case UNTRUSTED: default: - return "Untrusted"; + return "Untrusted "+getCode(); } } @@ -538,14 +543,20 @@ public class AxolotlService { return fingerprint; } - private SQLiteAxolotlStore.Trust getTrust() { + protected void setTrust(SQLiteAxolotlStore.Trust trust) { + sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust); + } + + protected SQLiteAxolotlStore.Trust getTrust() { return sqLiteAxolotlStore.getFingerprintTrust(fingerprint); } @Nullable public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) { byte[] plaintext = null; - switch (getTrust()) { + SQLiteAxolotlStore.Trust trust = getTrust(); + switch (trust) { + case INACTIVE: case UNDECIDED: case UNTRUSTED: case TRUSTED: @@ -574,6 +585,10 @@ public class AxolotlService { Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); } + if (plaintext != null && trust == SQLiteAxolotlStore.Trust.INACTIVE) { + setTrust(SQLiteAxolotlStore.Trust.TRUSTED); + } + break; case COMPROMISED: @@ -774,15 +789,32 @@ public class AxolotlService { return this.deviceIds.get(account.getJid().toBareJid()); } + private void setTrustOnSessions(final Jid jid, @NonNull final Set deviceIds, + final SQLiteAxolotlStore.Trust from, + final SQLiteAxolotlStore.Trust to) { + for(Integer deviceId:deviceIds) { + AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId); + XmppAxolotlSession session = sessions.get(address); + if (session != null && session.getFingerprint() != null + && session.getTrust() == from) { + session.setTrust(to); + } + } + } + public void registerDevices(final Jid jid, @NonNull final Set deviceIds) { if(deviceIds.contains(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, AxolotlService.getLogprefix(account)+"Adding Device ID:"+ jid + ":"+i); - } + Set expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString())); + expiredDevices.removeAll(deviceIds); + setTrustOnSessions(jid, expiredDevices, SQLiteAxolotlStore.Trust.TRUSTED, + SQLiteAxolotlStore.Trust.INACTIVE); + Set newDevices = new HashSet<>(deviceIds); + setTrustOnSessions(jid, newDevices, SQLiteAxolotlStore.Trust.INACTIVE, + SQLiteAxolotlStore.Trust.TRUSTED); this.deviceIds.put(jid, deviceIds); + mXmppConnectionService.keyStatusUpdated(); publishOwnDeviceIdIfNeeded(); } @@ -957,7 +989,7 @@ public class AxolotlService { } }); } - mXmppConnectionService.newKeysAvailable(); + mXmppConnectionService.keyStatusUpdated(); } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index e7df9e6a..fa0a8709 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -86,7 +86,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.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnPresencePacketReceived; import eu.siacs.conversations.xmpp.OnStatusChanged; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; @@ -309,8 +309,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 OnKeyStatusUpdated mOnKeyStatusUpdated = null; + private int keyStatusUpdatedListenerCount = 0; private SecureRandom mRandom; private OpenPgpServiceConnection pgpServiceConnection; private PgpEngine mPgpEngine = null; @@ -1372,30 +1372,31 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - public void setOnNewKeysAvailableListener(final OnNewKeysAvailable listener) { + public void setOnKeyStatusUpdatedListener(final OnKeyStatusUpdated listener) { synchronized (this) { if (checkListeners()) { switchToForeground(); } - this.mOnNewKeysAvailable = listener; - if (this.newKeysAvailableListenerCount < 2) { - this.newKeysAvailableListenerCount++; + this.mOnKeyStatusUpdated = listener; + if (this.keyStatusUpdatedListenerCount < 2) { + this.keyStatusUpdatedListenerCount++; } } } public void removeOnNewKeysAvailableListener() { synchronized (this) { - this.newKeysAvailableListenerCount--; - if (this.newKeysAvailableListenerCount <= 0) { - this.newKeysAvailableListenerCount = 0; - this.mOnNewKeysAvailable = null; + this.keyStatusUpdatedListenerCount--; + if (this.keyStatusUpdatedListenerCount <= 0) { + this.keyStatusUpdatedListenerCount = 0; + this.mOnKeyStatusUpdated = null; if (checkListeners()) { switchToBackground(); } } } } + public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) { synchronized (this) { if (checkListeners()) { @@ -1427,7 +1428,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa && this.mOnRosterUpdate == null && this.mOnUpdateBlocklist == null && this.mOnShowErrorToast == null - && this.mOnNewKeysAvailable == null); + && this.mOnKeyStatusUpdated == null); } private void switchToForeground() { @@ -2316,9 +2317,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - public void newKeysAvailable() { - if(mOnNewKeysAvailable != null) { - mOnNewKeysAvailable.onNewKeysAvailable(); + public void keyStatusUpdated() { + if(mOnKeyStatusUpdated != null) { + mOnKeyStatusUpdated.onKeyStatusUpdated(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 16e16cff..a0e02c1b 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -42,12 +42,13 @@ import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; -public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist { +public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated { public static final String ACTION_VIEW_CONTACT = "view_contact"; private Contact contact; @@ -468,4 +469,9 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd populateView(); } } + + @Override + public void onKeyStatusUpdated() { + refreshUi(); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index ac0f6616..aac8788f 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -35,12 +35,13 @@ import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.XmppConnection.Features; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.pep.Avatar; -public class EditAccountActivity extends XmppActivity implements OnAccountUpdate{ +public class EditAccountActivity extends XmppActivity implements OnAccountUpdate, OnKeyStatusUpdated { private AutoCompleteTextView mAccountJid; private EditText mPassword; @@ -618,4 +619,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate }); builder.create().show(); } + + @Override + public void onKeyStatusUpdated() { + refreshUi(); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index ccdef9c3..1bf07f3e 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -20,11 +20,11 @@ import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.T 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.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; -public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailable { +public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated { private Jid accountJid; private Jid contactJid; private boolean hasOtherTrustedKeys = false; @@ -215,7 +215,7 @@ public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailabl } @Override - public void onNewKeysAvailable() { + public void onKeyStatusUpdated() { final Account account = xmppConnectionService.findAccountByJid(accountJid); hasPendingFetches = false; getFingerprints(account); diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index a0a8d520..4cb6841b 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -83,7 +83,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.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -99,6 +99,7 @@ public abstract class XmppActivity extends Activity { protected int mPrimaryTextColor; protected int mSecondaryTextColor; + protected int mTertiaryTextColor; protected int mPrimaryBackgroundColor; protected int mSecondaryBackgroundColor; protected int mColorRed; @@ -294,8 +295,8 @@ 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); + if (this instanceof OnKeyStatusUpdated) { + this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this); } } @@ -318,7 +319,7 @@ public abstract class XmppActivity extends Activity { if (this instanceof XmppConnectionService.OnShowErrorToast) { this.xmppConnectionService.removeOnShowErrorToastListener(); } - if (this instanceof OnNewKeysAvailable) { + if (this instanceof OnKeyStatusUpdated) { this.xmppConnectionService.removeOnNewKeysAvailableListener(); } } @@ -349,6 +350,7 @@ public abstract class XmppActivity extends Activity { ExceptionHelper.init(getApplicationContext()); mPrimaryTextColor = getResources().getColor(R.color.black87); mSecondaryTextColor = getResources().getColor(R.color.black54); + mTertiaryTextColor = getResources().getColor(R.color.black12); mColorRed = getResources().getColor(R.color.red500); mColorOrange = getResources().getColor(R.color.orange500); mColorGreen = getResources().getColor(R.color.green500); @@ -668,10 +670,20 @@ public abstract class XmppActivity extends Activity { case TRUSTED: trustToggle.setChecked(trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED, false); trustToggle.setEnabled(true); + key.setTextColor(getPrimaryTextColor()); + keyType.setTextColor(getSecondaryTextColor()); break; case UNDECIDED: trustToggle.setChecked(false, false); trustToggle.setEnabled(false); + key.setTextColor(getPrimaryTextColor()); + keyType.setTextColor(getSecondaryTextColor()); + break; + case INACTIVE: + trustToggle.setChecked(true, false); + trustToggle.setEnabled(false); + key.setTextColor(getTertiaryTextColor()); + keyType.setTextColor(getTertiaryTextColor()); break; } @@ -824,6 +836,10 @@ public abstract class XmppActivity extends Activity { } }; + public int getTertiaryTextColor() { + return this.mTertiaryTextColor; + } + public int getSecondaryTextColor() { return this.mSecondaryTextColor; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnKeyStatusUpdated.java b/src/main/java/eu/siacs/conversations/xmpp/OnKeyStatusUpdated.java new file mode 100644 index 00000000..65ae133d --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/OnKeyStatusUpdated.java @@ -0,0 +1,5 @@ +package eu.siacs.conversations.xmpp; + +public interface OnKeyStatusUpdated { + public void onKeyStatusUpdated(); +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnNewKeysAvailable.java b/src/main/java/eu/siacs/conversations/xmpp/OnNewKeysAvailable.java deleted file mode 100644 index 59dc1c1e..00000000 --- a/src/main/java/eu/siacs/conversations/xmpp/OnNewKeysAvailable.java +++ /dev/null @@ -1,5 +0,0 @@ -package eu.siacs.conversations.xmpp; - -public interface OnNewKeysAvailable { - public void onNewKeysAvailable(); -} -- cgit v1.2.3 From f6281a182df6e251a326538702bf4f3b6f80a62b Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 Jul 2015 19:44:25 +0200 Subject: fixed npe in error message handling --- src/main/java/eu/siacs/conversations/parser/MessageParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index b81a62da..c2fdf5b8 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -206,7 +206,7 @@ public class MessageParser extends AbstractParser implements from.toBareJid(), packet.getId(), Message.STATUS_SEND_FAILED); - if (message.getEncryption() == Message.ENCRYPTION_OTR) { + if (message != null && message.getEncryption() == Message.ENCRYPTION_OTR) { message.getConversation().endOtrIfNeeded(); } } -- cgit v1.2.3 From 456d4c8b23dcf968322841c99d80c79ccda6571b Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 Jul 2015 23:49:35 +0200 Subject: made image file format configurable by Config.java --- src/main/java/eu/siacs/conversations/Config.java | 4 ++++ .../conversations/generator/MessageGenerator.java | 4 ---- .../siacs/conversations/persistance/FileBackend.java | 18 ++++++++++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 0f4b3404..b697e7fe 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -19,6 +19,10 @@ public final class Config { public static final int AVATAR_SIZE = 192; public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP; + public static final int IMAGE_SIZE = 1920; + public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.WEBP; + public static final int IMAGE_QUALITY = 75; + public static final int MESSAGE_MERGE_WINDOW = 20; public static final int PAGE_SIZE = 50; diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 040bc48b..bfa1dc85 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -1,7 +1,5 @@ package eu.siacs.conversations.generator; -import android.util.Log; - import net.java.otr4j.OtrException; import net.java.otr4j.session.Session; @@ -10,8 +8,6 @@ import java.util.Date; import java.util.Locale; import java.util.TimeZone; -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.Conversation; diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index d607345e..13bbb276 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -184,8 +184,18 @@ public class FileBackend { return this.copyImageToPrivateStorage(message, image, 0); } - private DownloadableFile copyImageToPrivateStorage(Message message, - Uri image, int sampleSize) throws FileCopyException { + private DownloadableFile copyImageToPrivateStorage(Message message,Uri image, int sampleSize) throws FileCopyException { + switch(Config.IMAGE_FORMAT) { + case JPEG: + message.setRelativeFilePath(message.getUuid()+".jpg"); + break; + case PNG: + message.setRelativeFilePath(message.getUuid()+".png"); + break; + case WEBP: + message.setRelativeFilePath(message.getUuid()+".webp"); + break; + } DownloadableFile file = getFile(message); file.getParentFile().mkdirs(); InputStream is = null; @@ -205,13 +215,13 @@ public class FileBackend { if (originalBitmap == null) { throw new FileCopyException(R.string.error_not_an_image_file); } - Bitmap scaledBitmap = resize(originalBitmap, IMAGE_SIZE); + Bitmap scaledBitmap = resize(originalBitmap, Config.IMAGE_SIZE); int rotation = getRotation(image); if (rotation > 0) { scaledBitmap = rotate(scaledBitmap, rotation); } - boolean success = scaledBitmap.compress(Bitmap.CompressFormat.WEBP, 75, os); + boolean success = scaledBitmap.compress(Config.IMAGE_FORMAT, Config.IMAGE_QUALITY, os); if (!success) { throw new FileCopyException(R.string.error_compressing_image); } -- cgit v1.2.3 From 4c1c2892c772fa4622195c81ad35c9e51ab02f35 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 22 Jul 2015 00:53:18 +0200 Subject: Disable trust toggle completely for INACTIVE keys --- src/main/java/eu/siacs/conversations/ui/XmppActivity.java | 1 + 1 file changed, 1 insertion(+) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 4cb6841b..83568510 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -680,6 +680,7 @@ public abstract class XmppActivity extends Activity { keyType.setTextColor(getSecondaryTextColor()); break; case INACTIVE: + trustToggle.setOnClickListener(null); trustToggle.setChecked(true, false); trustToggle.setEnabled(false); key.setTextColor(getTertiaryTextColor()); -- cgit v1.2.3 From c2813cea290b59036bf6d162552e6df3b715ab81 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 22 Jul 2015 01:00:20 +0200 Subject: Hide regenerate keys button Can re-enable it via Config.java setting --- src/main/java/eu/siacs/conversations/Config.java | 2 ++ .../conversations/ui/EditAccountActivity.java | 23 ++++++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index b697e7fe..d79a12f2 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -37,6 +37,8 @@ public final class Config { public static final boolean ENCRYPT_ON_HTTP_UPLOADED = false; + public static final boolean SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON = false; + public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2; public static final int MAM_MAX_MESSAGES = 500; diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index aac8788f..3c1f155d 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -29,6 +29,7 @@ import org.whispersystems.libaxolotl.IdentityKey; import java.util.Set; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; @@ -547,16 +548,18 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } } }); - this.mRegenerateAxolotlKeyButton - .setVisibility(View.VISIBLE); - this.mRegenerateAxolotlKeyButton - .setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(final View v) { - showRegenerateAxolotlKeyDialog(); - } - }); + if (Config.SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON) { + 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); } -- cgit v1.2.3 From a1e63944a20d88edc71049424538d61df4b7de9a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 22 Jul 2015 12:15:09 +0200 Subject: use 'interactive mode' when starting downloads from the context menu --- src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java | 1 - src/main/java/eu/siacs/conversations/ui/ConversationFragment.java | 2 +- src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index d7487af9..30d9a393 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -35,7 +35,6 @@ public class HttpDownloadConnection implements Transferable { private int mStatus = Transferable.STATUS_UNKNOWN; private boolean acceptedAutomatically = false; private int mProgress = 0; - private long mLastGuiRefresh = 0; public HttpDownloadConnection(HttpConnectionManager manager) { this.mHttpConnectionManager = manager; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index f40b06c8..f9144083 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -571,7 +571,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private void downloadFile(Message message) { activity.xmppConnectionService.getHttpConnectionManager() - .createNewDownloadConnection(message); + .createNewDownloadConnection(message,true); } private void cancelTransmission(Message message) { 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 679fb355..b158f0fe 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -573,7 +573,7 @@ public class MessageAdapter extends ArrayAdapter { Toast.LENGTH_SHORT).show(); } } else if (message.treatAsDownloadable() != Message.Decision.NEVER) { - activity.xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message); + activity.xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message,true); } } -- cgit v1.2.3 From 63206e6d4a0713719ff19cb38c93d091fe0c4735 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 22 Jul 2015 14:15:00 +0200 Subject: use type=chat more often to go along with new, simple carbon and mam rules * change chat states to type=chat and chat markers to type=chat * use same type as requesting stanza for delivery receipts (which should make them type=chat most of the time) --- .../siacs/conversations/generator/MessageGenerator.java | 11 +++++------ .../eu/siacs/conversations/parser/MessageParser.java | 16 ++++++++++------ 2 files changed, 15 insertions(+), 12 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index bfa1dc85..484fca00 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -115,6 +115,7 @@ public class MessageGenerator extends AbstractGenerator { public MessagePacket generateChatState(Conversation conversation) { final Account account = conversation.getAccount(); MessagePacket packet = new MessagePacket(); + packet.setType(MessagePacket.TYPE_CHAT); packet.setTo(conversation.getJid().toBareJid()); packet.setFrom(account.getJid()); packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); @@ -123,7 +124,7 @@ public class MessageGenerator extends AbstractGenerator { public MessagePacket confirm(final Account account, final Jid to, final String id) { MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_NORMAL); + packet.setType(MessagePacket.TYPE_CHAT); packet.setTo(to); packet.setFrom(account.getJid()); Element received = packet.addChild("displayed","urn:xmpp:chat-markers:0"); @@ -131,8 +132,7 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket conferenceSubject(Conversation conversation, - String subject) { + public MessagePacket conferenceSubject(Conversation conversation,String subject) { MessagePacket packet = new MessagePacket(); packet.setType(MessagePacket.TYPE_GROUPCHAT); packet.setTo(conversation.getJid().toBareJid()); @@ -166,10 +166,9 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket received(Account account, - MessagePacket originalMessage, String namespace) { + public MessagePacket received(Account account, MessagePacket originalMessage, String namespace, int type) { MessagePacket receivedPacket = new MessagePacket(); - receivedPacket.setType(MessagePacket.TYPE_NORMAL); + receivedPacket.setType(type); receivedPacket.setTo(originalMessage.getFrom()); receivedPacket.setFrom(account.getJid()); Element received = receivedPacket.addChild("received", namespace); diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index c2fdf5b8..6e7b3276 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -263,7 +263,7 @@ public class MessageParser extends AbstractParser implements timestamp = AbstractParser.getTimestamp(packet, System.currentTimeMillis()); } final String body = packet.getBody(); - final Element mucUserElement = packet.findChild("x","http://jabber.org/protocol/muc#user"); + 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; @@ -369,15 +369,19 @@ public class MessageParser extends AbstractParser implements mXmppConnectionService.updateConversationUi(); } - if (mXmppConnectionService.confirmMessages() && remoteMsgId != null && !isForwarded) { + if (mXmppConnectionService.confirmMessages() && remoteMsgId != null && !isForwarded && !isTypeGroupChat) { if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) { - MessagePacket receipt = mXmppConnectionService - .getMessageGenerator().received(account, packet, "urn:xmpp:chat-markers:0"); + MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, + packet, + "urn:xmpp:chat-markers:0", + MessagePacket.TYPE_CHAT); mXmppConnectionService.sendMessagePacket(account, receipt); } if (packet.hasChild("request", "urn:xmpp:receipts")) { - MessagePacket receipt = mXmppConnectionService - .getMessageGenerator().received(account, packet, "urn:xmpp:receipts"); + MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, + packet, + "urn:xmpp:receipts", + packet.getType()); mXmppConnectionService.sendMessagePacket(account, receipt); } } -- cgit v1.2.3 From 9c1c86ed444cfa860b6e958849dd6a55b9a16a04 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 22 Jul 2015 14:17:02 +0200 Subject: add no-store to chat states --- src/main/java/eu/siacs/conversations/generator/MessageGenerator.java | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 484fca00..04591672 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -119,6 +119,8 @@ public class MessageGenerator extends AbstractGenerator { packet.setTo(conversation.getJid().toBareJid()); packet.setFrom(account.getJid()); packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); + packet.addChild("no-store", "urn:xmpp:hints"); + packet.addChild("no-storage", "urn:xmpp:hints"); //wrong! don't copy this. Its *store* return packet; } -- cgit v1.2.3 From db05d264334b548ce2f307d614e39aabd775bb4f Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 22 Jul 2015 15:02:53 +0200 Subject: Always build own device session automatically --- .../conversations/crypto/axolotl/AxolotlService.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'src/main/java/eu') 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 c30a7ab8..c6f74538 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -803,8 +803,16 @@ public class AxolotlService { } public void registerDevices(final Jid jid, @NonNull final Set deviceIds) { - if(deviceIds.contains(getOwnDeviceId())) { - deviceIds.remove(getOwnDeviceId()); + if(jid.toBareJid().equals(account.getJid().toBareJid())) { + if (deviceIds.contains(getOwnDeviceId())) { + deviceIds.remove(getOwnDeviceId()); + } + for(Integer deviceId : deviceIds) { + AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(),deviceId); + if(sessions.get(ownDeviceAddress) == null) { + buildSessionFromPEP(null, ownDeviceAddress, false); + } + } } Set expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString())); expiredDevices.removeAll(deviceIds); @@ -976,11 +984,10 @@ public class AxolotlService { 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); + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(),0); if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) - && !fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) { - if (flushWaitingQueueAfterFetch) { + && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) { + if (flushWaitingQueueAfterFetch && conversation != null) { conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_AXOLOTL, new Conversation.OnMessageFound() { @Override -- cgit v1.2.3 From f7634a85be516feef753bdb32fed8df7da1573ed Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 22 Jul 2015 15:31:00 +0200 Subject: treat private, non-anonymous mucs like 1:1 chats notification wise --- src/main/java/eu/siacs/conversations/entities/Conversation.java | 7 +++++++ .../java/eu/siacs/conversations/services/NotificationService.java | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 2efd8a29..6d99e358 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -519,6 +519,13 @@ public class Conversation extends AbstractEntity implements Blockable { return getContact().getOtrFingerprints().contains(getOtrFingerprint()); } + /** + * short for is Private and Non-anonymous + */ + public boolean isPnNA() { + return mode == MODE_SINGLE || (getMucOptions().membersOnly() && getMucOptions().nonanonymous()); + } + public synchronized MucOptions getMucOptions() { if (this.mucOptions == null) { this.mucOptions = new MucOptions(this); diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 956f704e..47f0347f 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -64,7 +64,7 @@ public class NotificationService { return (message.getStatus() == Message.STATUS_RECEIVED) && notificationsEnabled() && !message.getConversation().isMuted() - && (message.getConversation().getMode() == Conversation.MODE_SINGLE + && (message.getConversation().isPnNA() || conferenceNotificationsEnabled() || wasHighlightedOrPrivate(message) ); -- cgit v1.2.3 From cd204d5931a6cf0289d2df120f278864b8b94fdf Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 22 Jul 2015 15:57:17 +0200 Subject: show warning when trying to highlight users that have left the conference --- .../eu/siacs/conversations/entities/MucOptions.java | 9 +++++++++ .../eu/siacs/conversations/ui/ConversationFragment.java | 17 ++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 6ff6b8c9..52a862ef 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -264,6 +264,15 @@ public class MucOptions { users.add(user); } + public boolean isUserInRoom(String name) { + for (int i = 0; i < users.size(); ++i) { + if (users.get(i).getName().equals(name)) { + return true; + } + } + return false; + } + public void processPacket(PresencePacket packet, PgpEngine pgp) { final Jid from = packet.getFrom(); if (!from.isBareJid()) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index f9144083..6ff14d81 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -385,11 +385,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (message.getStatus() <= Message.STATUS_RECEIVED) { if (message.getConversation().getMode() == Conversation.MODE_MULTI) { if (message.getCounterpart() != null) { - if (!message.getCounterpart().isBareJid()) { - highlightInConference(message.getCounterpart().getResourcepart()); - } else { - highlightInConference(message.getCounterpart().toString()); + String user = message.getCounterpart().isBareJid() ? message.getCounterpart().toString() : message.getCounterpart().getResourcepart(); + if (!message.getConversation().getMucOptions().isUserInRoom(user)) { + Toast.makeText(activity,activity.getString(R.string.user_has_left_conference,user),Toast.LENGTH_SHORT).show(); } + highlightInConference(user); } } else { activity.switchToContactDetails(message.getContact()); @@ -410,7 +410,14 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (message.getStatus() <= Message.STATUS_RECEIVED) { if (message.getConversation().getMode() == Conversation.MODE_MULTI) { if (message.getCounterpart() != null) { - privateMessageWith(message.getCounterpart()); + String user = message.getCounterpart().getResourcepart(); + if (user != null) { + if (message.getConversation().getMucOptions().isUserInRoom(user)) { + privateMessageWith(message.getCounterpart()); + } else { + Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user), Toast.LENGTH_SHORT).show(); + } + } } } } else { -- cgit v1.2.3 From c32162c2808a9bdef841cae706a8de9bcf5391a1 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 23 Jul 2015 14:02:25 +0200 Subject: switch/case can't deal with null pointers --- src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java index 7b36fc49..398102e1 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java @@ -39,6 +39,9 @@ public class IqPacket extends AbstractStanza { public TYPE getType() { final String type = getAttribute("type"); + if (type == null) { + return TYPE.INVALID; + } switch (type) { case "error": return TYPE.ERROR; -- cgit v1.2.3 From e5fae429fa45a065979103eaf95f0c8b1edc4ab4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 24 Jul 2015 14:43:13 +0200 Subject: call refreshUi directly --- src/main/java/eu/siacs/conversations/ui/ConversationFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 6ff14d81..04429421 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -832,7 +832,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } catch (final NoSuchElementException ignored) { } - activity.xmppConnectionService.updateConversationUi(); + activity.refreshUi(); } }); } -- cgit v1.2.3 From 9c94c9ad8fe10aa1ffeb122b1db114680a7d95a2 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 24 Jul 2015 19:06:47 +0200 Subject: rewrote dns fallback --- src/main/java/eu/siacs/conversations/utils/DNSHelper.java | 15 ++++++--------- .../java/eu/siacs/conversations/xmpp/XmppConnection.java | 3 +++ 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java index 36d03b30..60e1f0be 100644 --- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java @@ -38,17 +38,14 @@ public class DNSHelper { public static Bundle getSRVRecord(final Jid jid) throws IOException { final String host = jid.getDomainpart(); String dns[] = client.findDNS(); - - if (dns != null) { - for (String dnsserver : dns) { - InetAddress ip = InetAddress.getByName(dnsserver); - Bundle b = queryDNS(host, ip); - if (b.containsKey("values")) { - return b; - } + for (int i = 0; i < dns.length; ++i) { + InetAddress ip = InetAddress.getByName(dns[i]); + Bundle b = queryDNS(host, ip); + if (b.containsKey("values") || i == dns.length - 1) { + return b; } } - return queryDNS(host, InetAddress.getByName("8.8.8.8")); + return null; } public static Bundle queryDNS(String host, InetAddress dnsServer) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 7c81d988..c41c5174 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -164,6 +164,9 @@ public class XmppConnection implements Runnable { } } else { final Bundle result = DNSHelper.getSRVRecord(account.getServer()); + if (result == null) { + throw new IOException("unhandled exception in DNS resolver"); + } final ArrayList values = result.getParcelableArrayList("values"); if ("timeout".equals(result.getString("error"))) { throw new IOException("timeout in dns"); -- cgit v1.2.3 From ff0c114cd66b0fcae6fe8d6598fde48d9c1c5bd4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 Jul 2015 12:54:54 +0200 Subject: set time on resend to current time fixes #1298 fixes #919 --- .../java/eu/siacs/conversations/services/XmppConnectionService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index fa0a8709..cadcf851 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -2533,8 +2533,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } for (final Message msg : messages) { + msg.setTime(System.currentTimeMillis()); markMessage(msg, Message.STATUS_WAITING); - this.resendMessage(msg,true); + this.resendMessage(msg,false); } } -- cgit v1.2.3 From 6b8e1ecb9532509ac6bca6ca3665f6ac3f560332 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 Jul 2015 14:44:11 +0200 Subject: log reason for message failure --- src/main/java/eu/siacs/conversations/parser/MessageParser.java | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 6e7b3276..f0659511 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -202,6 +202,13 @@ public class MessageParser extends AbstractParser implements if (packet.getType() == MessagePacket.TYPE_ERROR) { Jid from = packet.getFrom(); if (from != null) { + Element error = packet.findChild("error"); + String text = error == null ? null : error.findChildContent("text"); + if (text != null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending message to "+ from+ " failed - " + text); + } else if (error != null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending message to "+ from+ " failed - " + error); + } Message message = mXmppConnectionService.markMessage(account, from.toBareJid(), packet.getId(), -- cgit v1.2.3 From 8924c448d19d44a3a4a6f09a76d2e09130f22f87 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 Jul 2015 22:26:29 +0200 Subject: changed logging. (work around logcat null pointer --- src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java | 2 +- src/main/java/eu/siacs/conversations/utils/DNSHelper.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index c25bf13a..c9fb3d8c 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -194,7 +194,7 @@ public class HttpUploadConnection implements Transferable { fail(); } } catch (IOException e) { - Log.d(Config.LOGTAG, e.getMessage()); + Log.d(Config.LOGTAG,"http upload failed "+e.getMessage()); fail(); } finally { FileBackend.close(is); diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java index 60e1f0be..4d0dd3da 100644 --- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java @@ -129,7 +129,6 @@ public class DNSHelper { } catch (SocketTimeoutException e) { bundle.putString("error", "timeout"); } catch (Exception e) { - e.printStackTrace(); bundle.putString("error", "unhandled"); } return bundle; -- cgit v1.2.3 From 8f14d2bfbd1e3e6697a5afccfc2512f98b55b702 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 Jul 2015 23:00:30 +0200 Subject: removed recursion in message.getMerged*() --- .../eu/siacs/conversations/entities/Message.java | 32 +++++++++++++--------- 1 file changed, 19 insertions(+), 13 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 698775c8..b4bb31a8 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -416,11 +416,14 @@ public class Message extends AbstractEntity { } public String getMergedBody() { - final Message next = this.next(); - if (this.mergeable(next)) { - return getBody().trim() + MERGE_SEPARATOR + next.getMergedBody(); + StringBuilder body = new StringBuilder(this.body.trim()); + Message current = this; + while(current.mergeable(current.next())) { + current = current.next(); + body.append(MERGE_SEPARATOR); + body.append(current.getBody().trim()); } - return getBody().trim(); + return body.toString(); } public boolean hasMeCommand() { @@ -428,20 +431,23 @@ public class Message extends AbstractEntity { } public int getMergedStatus() { - final Message next = this.next(); - if (this.mergeable(next)) { - return next.getStatus(); + int status = this.status; + Message current = this; + while(current.mergeable(current.next())) { + current = current.next(); + status = current.status; } - return getStatus(); + return status; } public long getMergedTimeSent() { - Message next = this.next(); - if (this.mergeable(next)) { - return next.getMergedTimeSent(); - } else { - return getTimeSent(); + long time = this.timeSent; + Message current = this; + while(current.mergeable(current.next())) { + current = current.next(); + time = current.timeSent; } + return time; } public boolean wasMergedIntoPrevious() { -- cgit v1.2.3 From 17bc4fb6cd2f52ce7bfbc03540b88cad3ebc5f26 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 29 Jul 2015 01:57:08 +0200 Subject: show http downloaded images in notification --- .../java/eu/siacs/conversations/services/NotificationService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 47f0347f..90e4d216 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -332,9 +332,10 @@ public class NotificationService { private Message getImage(final Iterable messages) { for (final Message message : messages) { - if (message.getType() == Message.TYPE_IMAGE + if (message.getType() != Message.TYPE_TEXT && message.getTransferable() == null - && message.getEncryption() != Message.ENCRYPTION_PGP) { + && message.getEncryption() != Message.ENCRYPTION_PGP + && message.getFileParams().height > 0) { return message; } } -- cgit v1.2.3 From efcefc2e6301f6255ebcb3e11f0f0a51731bacca Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Tue, 28 Jul 2015 22:00:54 +0200 Subject: Refactor out inner classes, cache trust store Moves SQLiteAxolotlStore and XmppAxolotlSession into proper classes. IdentityKeys trust statuses are now cached in an LruCache to prevent hammering the database when rendering the UI. --- .../crypto/axolotl/AxolotlService.java | 564 +-------------------- .../crypto/axolotl/SQLiteAxolotlStore.java | 473 +++++++++++++++++ .../crypto/axolotl/XmppAxolotlMessage.java | 8 +- .../crypto/axolotl/XmppAxolotlSession.java | 131 +++++ .../eu/siacs/conversations/entities/Message.java | 4 +- .../conversations/persistance/DatabaseBackend.java | 261 +++++----- .../conversations/ui/ConversationActivity.java | 2 +- .../siacs/conversations/ui/TrustKeysActivity.java | 2 +- .../eu/siacs/conversations/ui/XmppActivity.java | 18 +- .../conversations/ui/adapter/MessageAdapter.java | 6 +- 10 files changed, 760 insertions(+), 709 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java create mode 100644 src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java (limited to 'src/main/java/eu') 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 c6f74538..d1bfe2d4 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -6,28 +6,15 @@ import android.util.Log; import org.bouncycastle.jce.provider.BouncyCastleProvider; 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; import org.whispersystems.libaxolotl.util.KeyHelper; @@ -73,547 +60,6 @@ public class AxolotlService { private final FetchStatusMap fetchStatusMap; private final SerialSingleThreadExecutor executor; - 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 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 FINGERPRINT = "fingerprint"; - public static final String NAME = "name"; - public static final String TRUSTED = "trusted"; - public static final String OWN = "ownkey"; - - 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 IdentityKeyPair identityKeyPair; - private int localRegistrationId; - private int currentPreKeyId = 0; - - public enum Trust { - UNDECIDED(0), - TRUSTED(1), - UNTRUSTED(2), - COMPROMISED(3), - INACTIVE(4); - - private static final Map trustsByValue = new HashMap<>(); - - static { - for (Trust trust : Trust.values()) { - trustsByValue.put(trust.getCode(), trust); - } - } - - private final int code; - - Trust(int code){ - this.code = code; - } - - public int getCode() { - return this.code; - } - - public String toString() { - switch(this){ - case UNDECIDED: - return "Trust undecided "+getCode(); - case TRUSTED: - return "Trusted "+getCode(); - case COMPROMISED: - return "Compromised "+getCode(); - case INACTIVE: - return "Inactive "+getCode(); - case UNTRUSTED: - default: - return "Untrusted "+getCode(); - } - } - - public static Trust fromBoolean(Boolean trusted) { - return trusted?TRUSTED:UNTRUSTED; - } - - public static Trust fromCode(int code) { - return trustsByValue.get(code); - } - }; - - private static IdentityKeyPair generateIdentityKeyPair() { - Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Generating axolotl IdentityKeyPair..."); - ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); - IdentityKeyPair ownKey = new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), - identityKeyPairKeys.getPrivateKey()); - return ownKey; - } - - private static int generateRegistrationId() { - Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Generating axolotl registration ID..."); - int reg_id = KeyHelper.generateRegistrationId(true); - return reg_id; - } - - public SQLiteAxolotlStore(Account account, XmppConnectionService service) { - this.account = account; - this.mXmppConnectionService = service; - this.localRegistrationId = loadRegistrationId(); - this.currentPreKeyId = loadCurrentPreKeyId(); - for (SignedPreKeyRecord record : loadSignedPreKeys()) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got Axolotl signed prekey record:" + record.getId()); - } - } - - public int getCurrentPreKeyId() { - return currentPreKeyId; - } - - // -------------------------------------- - // IdentityKeyStore - // -------------------------------------- - - private IdentityKeyPair loadIdentityKeyPair() { - String ownName = account.getJid().toBareJid().toString(); - IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account, - ownName); - - if (ownKey != null) { - return ownKey; - } else { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve axolotl key for account " + ownName); - ownKey = generateIdentityKeyPair(); - mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownName, ownKey); - } - return ownKey; - } - - private int loadRegistrationId() { - return loadRegistrationId(false); - } - - private int loadRegistrationId(boolean regenerate) { - String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID); - int reg_id; - 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()); - 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, AxolotlService.getLogprefix(account)+"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.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve current prekey id for account " + account.getJid()); - reg_id = 0; - } - return reg_id; - } - - public void regenerate() { - mXmppConnectionService.databaseBackend.wipeAxolotlDb(account); - account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0)); - identityKeyPair = loadIdentityKeyPair(); - localRegistrationId = loadRegistrationId(true); - currentPreKeyId = 0; - mXmppConnectionService.updateAccountUi(); - } - - /** - * Get the local client's identity key pair. - * - * @return The local client's persistent identity key pair. - */ - @Override - public IdentityKeyPair getIdentityKeyPair() { - if(identityKeyPair == null) { - identityKeyPair = loadIdentityKeyPair(); - } - 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) { - if(!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) { - mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey); - } - } - - /** - * 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) { - return true; - } - - public Trust getFingerprintTrust(String fingerprint) { - return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint); - } - - public void setFingerprintTrust(String fingerprint, Trust trust) { - mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust); - } - - public Set getContactUndecidedKeys(String bareJid, Trust trust) { - return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust); - } - - public long getContactNumTrustedKeys(String bareJid) { - return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid); - } - - // -------------------------------------- - // 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: " + 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, AxolotlService.getLogprefix(account)+"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 final SessionCipher cipher; - 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; - } - - public Integer getPreKeyId() { - return preKeyId; - } - - public void resetPreKeyId() { - - preKeyId = null; - } - - public String getFingerprint() { - return fingerprint; - } - - protected void setTrust(SQLiteAxolotlStore.Trust trust) { - sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust); - } - - protected SQLiteAxolotlStore.Trust getTrust() { - return sqLiteAxolotlStore.getFingerprintTrust(fingerprint); - } - - @Nullable - public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) { - byte[] plaintext = null; - SQLiteAxolotlStore.Trust trust = getTrust(); - switch (trust) { - case INACTIVE: - case UNDECIDED: - case UNTRUSTED: - case TRUSTED: - try { - try { - PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); - 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"); - WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); - plaintext = cipher.decrypt(message); - } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"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()); - } - - if (plaintext != null && trust == SQLiteAxolotlStore.Trust.INACTIVE) { - setTrust(SQLiteAxolotlStore.Trust.TRUSTED); - } - - break; - - case COMPROMISED: - default: - // ignore - break; - } - return plaintext; - } - - @Nullable - public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(@NonNull byte[] outgoingMessage) { - SQLiteAxolotlStore.Trust trust = getTrust(); - if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { - CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); - XmppAxolotlMessage.XmppAxolotlMessageHeader header = - new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), - ciphertextMessage.serialize()); - return header; - } else { - return null; - } - } - } - private static class AxolotlAddressMap { protected Map> map; protected final Object MAP_LOCK = new Object(); @@ -741,11 +187,11 @@ public class AxolotlService { } public Set getKeysWithTrust(SQLiteAxolotlStore.Trust trust) { - return axolotlStore.getContactUndecidedKeys(account.getJid().toBareJid().toString(), trust); + return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust); } public Set getKeysWithTrust(SQLiteAxolotlStore.Trust trust, Contact contact) { - return axolotlStore.getContactUndecidedKeys(contact.getJid().toBareJid().toString(), trust); + return axolotlStore.getContactKeysWithTrust(contact.getJid().toBareJid().toString(), trust); } public long getNumTrustedKeys(Contact contact) { @@ -782,7 +228,7 @@ public class AxolotlService { } public int getOwnDeviceId() { - return axolotlStore.loadRegistrationId(); + return axolotlStore.getLocalRegistrationId(); } public Set getOwnDeviceIds() { @@ -1139,7 +585,7 @@ public class AxolotlService { } Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign headers..."); for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) { - Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.remoteAddress.toString()); + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); //if(!session.isTrusted()) { // TODO: handle this properly // continue; @@ -1148,7 +594,7 @@ public class AxolotlService { } Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own headers..."); for (XmppAxolotlSession session : findOwnSessions()) { - Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.remoteAddress.toString()); + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); // if(!session.isTrusted()) { // TODO: handle this properly // continue; diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java new file mode 100644 index 00000000..0c9c4e65 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java @@ -0,0 +1,473 @@ +package eu.siacs.conversations.crypto.axolotl; + +import android.util.Log; +import android.util.LruCache; + +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.IdentityKeyPair; +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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.services.XmppConnectionService; + +public 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 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 FINGERPRINT = "fingerprint"; + public static final String NAME = "name"; + public static final String TRUSTED = "trusted"; + public static final String OWN = "ownkey"; + + public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id"; + public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id"; + + private static final int NUM_TRUSTS_TO_CACHE = 100; + + private final Account account; + private final XmppConnectionService mXmppConnectionService; + + private IdentityKeyPair identityKeyPair; + private int localRegistrationId; + private int currentPreKeyId = 0; + + private final LruCache trustCache = + new LruCache(NUM_TRUSTS_TO_CACHE) { + @Override + protected Trust create(String fingerprint) { + return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint); + } + }; + + public enum Trust { + UNDECIDED(0), + TRUSTED(1), + UNTRUSTED(2), + COMPROMISED(3), + INACTIVE(4); + + private static final Map trustsByValue = new HashMap<>(); + + static { + for (Trust trust : Trust.values()) { + trustsByValue.put(trust.getCode(), trust); + } + } + + private final int code; + + Trust(int code) { + this.code = code; + } + + public int getCode() { + return this.code; + } + + public String toString() { + switch (this) { + case UNDECIDED: + return "Trust undecided " + getCode(); + case TRUSTED: + return "Trusted " + getCode(); + case COMPROMISED: + return "Compromised " + getCode(); + case INACTIVE: + return "Inactive " + getCode(); + case UNTRUSTED: + default: + return "Untrusted " + getCode(); + } + } + + public static Trust fromBoolean(Boolean trusted) { + return trusted ? TRUSTED : UNTRUSTED; + } + + public static Trust fromCode(int code) { + return trustsByValue.get(code); + } + } + + private static IdentityKeyPair generateIdentityKeyPair() { + Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl IdentityKeyPair..."); + ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); + return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), + identityKeyPairKeys.getPrivateKey()); + } + + private static int generateRegistrationId() { + Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl registration ID..."); + return KeyHelper.generateRegistrationId(true); + } + + public SQLiteAxolotlStore(Account account, XmppConnectionService service) { + this.account = account; + this.mXmppConnectionService = service; + this.localRegistrationId = loadRegistrationId(); + this.currentPreKeyId = loadCurrentPreKeyId(); + for (SignedPreKeyRecord record : loadSignedPreKeys()) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId()); + } + } + + public int getCurrentPreKeyId() { + return currentPreKeyId; + } + + // -------------------------------------- + // IdentityKeyStore + // -------------------------------------- + + private IdentityKeyPair loadIdentityKeyPair() { + String ownName = account.getJid().toBareJid().toString(); + IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account, + ownName); + + if (ownKey != null) { + return ownKey; + } else { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl key for account " + ownName); + ownKey = generateIdentityKeyPair(); + mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownName, ownKey); + } + return ownKey; + } + + private int loadRegistrationId() { + return loadRegistrationId(false); + } + + private int loadRegistrationId(boolean regenerate) { + String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID); + int reg_id; + 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()); + 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, AxolotlService.getLogprefix(account) + "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.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid()); + reg_id = 0; + } + return reg_id; + } + + public void regenerate() { + mXmppConnectionService.databaseBackend.wipeAxolotlDb(account); + trustCache.evictAll(); + account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0)); + identityKeyPair = loadIdentityKeyPair(); + localRegistrationId = loadRegistrationId(true); + currentPreKeyId = 0; + mXmppConnectionService.updateAccountUi(); + } + + /** + * Get the local client's identity key pair. + * + * @return The local client's persistent identity key pair. + */ + @Override + public IdentityKeyPair getIdentityKeyPair() { + if (identityKeyPair == null) { + identityKeyPair = loadIdentityKeyPair(); + } + 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) { + if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) { + mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey); + } + } + + /** + * 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) { + return true; + } + + public Trust getFingerprintTrust(String fingerprint) { + return (fingerprint == null)? null : trustCache.get(fingerprint); + } + + public void setFingerprintTrust(String fingerprint, Trust trust) { + mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust); + trustCache.remove(fingerprint); + } + + public Set getContactKeysWithTrust(String bareJid, Trust trust) { + return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust); + } + + public long getContactNumTrustedKeys(String bareJid) { + return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid); + } + + // -------------------------------------- + // 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) { + AxolotlAddress address = new AxolotlAddress(name, 0); + mXmppConnectionService.databaseBackend.deleteAllSessions(account, + address); + } + + // -------------------------------------- + // 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, AxolotlService.getLogprefix(account) + "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); + } +} 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 24afeaea..182c8f12 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -67,11 +67,11 @@ public class XmppAxolotlMessage { } public static class XmppAxolotlPlaintextMessage { - private final AxolotlService.XmppAxolotlSession session; + private final XmppAxolotlSession session; private final String plaintext; private final String fingerprint; - public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext, String fingerprint) { + public XmppAxolotlPlaintextMessage(XmppAxolotlSession session, String plaintext, String fingerprint) { this.session = session; this.plaintext = plaintext; this.fingerprint = fingerprint; @@ -81,7 +81,7 @@ public class XmppAxolotlMessage { return plaintext; } - public AxolotlService.XmppAxolotlSession getSession() { + public XmppAxolotlSession getSession() { return session; } @@ -180,7 +180,7 @@ public class XmppAxolotlMessage { } - public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) throws CryptoFailedException { + public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, byte[] key, String fingerprint) throws CryptoFailedException { XmppAxolotlPlaintextMessage plaintextMessage = null; try { diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java new file mode 100644 index 00000000..b8d7dcba --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -0,0 +1,131 @@ +package eu.siacs.conversations.crypto.axolotl; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.DuplicateMessageException; +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.SessionCipher; +import org.whispersystems.libaxolotl.UntrustedIdentityException; +import org.whispersystems.libaxolotl.protocol.CiphertextMessage; +import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; +import org.whispersystems.libaxolotl.protocol.WhisperMessage; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; + +public class XmppAxolotlSession { + private final SessionCipher cipher; + private Integer preKeyId = null; + private final SQLiteAxolotlStore sqLiteAxolotlStore; + + public AxolotlAddress getRemoteAddress() { + return remoteAddress; + } + + 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; + } + + public Integer getPreKeyId() { + return preKeyId; + } + + public void resetPreKeyId() { + + preKeyId = null; + } + + public String getFingerprint() { + return fingerprint; + } + + protected void setTrust(SQLiteAxolotlStore.Trust trust) { + sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust); + } + + protected SQLiteAxolotlStore.Trust getTrust() { + return sqLiteAxolotlStore.getFingerprintTrust(fingerprint); + } + + @Nullable + public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) { + byte[] plaintext = null; + SQLiteAxolotlStore.Trust trust = getTrust(); + switch (trust) { + case INACTIVE: + case UNDECIDED: + case UNTRUSTED: + case TRUSTED: + try { + try { + PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); + 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"); + WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); + plaintext = cipher.decrypt(message); + } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "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()); + } + + if (plaintext != null && trust == SQLiteAxolotlStore.Trust.INACTIVE) { + setTrust(SQLiteAxolotlStore.Trust.TRUSTED); + } + + break; + + case COMPROMISED: + default: + // ignore + break; + } + return plaintext; + } + + @Nullable + public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(@NonNull byte[] outgoingMessage) { + SQLiteAxolotlStore.Trust trust = getTrust(); + if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { + CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); + XmppAxolotlMessage.XmppAxolotlMessageHeader header = + new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), + ciphertextMessage.serialize()); + return header; + } else { + return null; + } + } +} diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index b4bb31a8..14f96296 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -8,7 +8,7 @@ import java.net.URL; import java.util.Arrays; import eu.siacs.conversations.Config; -import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.UIHelper; @@ -689,6 +689,6 @@ public class Message extends AbstractEntity { public boolean isTrusted() { return conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint) - == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED; + == SQLiteAxolotlStore.Trust.TRUSTED; } } diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 3120c008..99dbcf34 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -27,6 +27,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -55,56 +56,56 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Contact.JID + ") ON CONFLICT REPLACE);"; private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE " - + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME + "(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " - + AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, " - + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + + SQLiteAxolotlStore.PREKEY_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " - + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " - + AxolotlService.SQLiteAxolotlStore.ID + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.ID + ") ON CONFLICT REPLACE" +");"; private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE " - + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " - + AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, " - + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " - + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " - + AxolotlService.SQLiteAxolotlStore.ID + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.ID + ") ON CONFLICT REPLACE"+ ");"; 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.KEY + " TEXT, FOREIGN KEY(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + + SQLiteAxolotlStore.SESSION_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.NAME + " TEXT, " + + SQLiteAxolotlStore.DEVICE_ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " - + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " - + AxolotlService.SQLiteAxolotlStore.NAME + ", " - + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.NAME + ", " + + SQLiteAxolotlStore.DEVICE_ID + ") 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.FINGERPRINT + " TEXT, " - + AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, " - + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + + SQLiteAxolotlStore.IDENTITIES_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.NAME + " TEXT, " + + SQLiteAxolotlStore.OWN + " INTEGER, " + + SQLiteAxolotlStore.FINGERPRINT + " TEXT, " + + SQLiteAxolotlStore.TRUSTED + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " - + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " - + AxolotlService.SQLiteAxolotlStore.NAME + ", " - + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.NAME + ", " + + SQLiteAxolotlStore.FINGERPRINT + ") ON CONFLICT IGNORE" +");"; @@ -567,11 +568,11 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] selectionArgs = {account.getUuid(), contact.getName(), Integer.toString(contact.getDeviceId())}; - Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, + Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME, columns, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ", + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ? AND " + + SQLiteAxolotlStore.DEVICE_ID + " = ? ", selectionArgs, null, null, null); @@ -584,7 +585,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { if(cursor.getCount() != 0) { cursor.moveToFirst(); try { - session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); + session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); } catch (IOException e) { cursor.close(); throw new AssertionError(e); @@ -597,19 +598,19 @@ public class DatabaseBackend extends SQLiteOpenHelper { public List getSubDeviceSessions(Account account, AxolotlAddress contact) { List devices = new ArrayList<>(); final SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {AxolotlService.SQLiteAxolotlStore.DEVICE_ID}; + String[] columns = {SQLiteAxolotlStore.DEVICE_ID}; String[] selectionArgs = {account.getUuid(), contact.getName()}; - Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, + Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME, columns, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.NAME + " = ?", + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ?", selectionArgs, null, null, null); while(cursor.moveToNext()) { devices.add(cursor.getInt( - cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.DEVICE_ID))); + cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID))); } cursor.close(); @@ -626,11 +627,11 @@ public class DatabaseBackend extends SQLiteOpenHelper { 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, Base64.encodeToString(session.serialize(),Base64.DEFAULT)); - values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); - db.insert(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, null, values); + values.put(SQLiteAxolotlStore.NAME, contact.getName()); + values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(),Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values); } public void deleteSession(Account account, AxolotlAddress contact) { @@ -638,30 +639,30 @@ public class DatabaseBackend extends SQLiteOpenHelper { 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 + " = ? ", + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ? AND " + + 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 + " = ?", + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.NAME + " = ?", args); } private Cursor getCursorForPreKey(Account account, int preKeyId) { SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; + String[] columns = {SQLiteAxolotlStore.KEY}; String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)}; - Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, + Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME, columns, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " - + AxolotlService.SQLiteAxolotlStore.ID + "=?", + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", selectionArgs, null, null, null); @@ -674,7 +675,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { if(cursor.getCount() != 0) { cursor.moveToFirst(); try { - record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); + record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); } catch (IOException e ) { throw new AssertionError(e); } @@ -693,28 +694,28 @@ public class DatabaseBackend extends SQLiteOpenHelper { 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, Base64.encodeToString(record.serialize(),Base64.DEFAULT)); - values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); - db.insert(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, null, values); + values.put(SQLiteAxolotlStore.ID, record.getId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(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 + "=?", + db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", args); } private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) { SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; + String[] columns = {SQLiteAxolotlStore.KEY}; String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)}; - Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, columns, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " + AxolotlService.SQLiteAxolotlStore.ID + "=?", + SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?", selectionArgs, null, null, null); @@ -727,7 +728,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { if(cursor.getCount() != 0) { cursor.moveToFirst(); try { - record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); + record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); } catch (IOException e ) { throw new AssertionError(e); } @@ -739,17 +740,17 @@ public class DatabaseBackend extends SQLiteOpenHelper { public List loadSignedPreKeys(Account account) { List prekeys = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; + String[] columns = {SQLiteAxolotlStore.KEY}; String[] selectionArgs = {account.getUuid()}; - Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, columns, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=?", + SQLiteAxolotlStore.ACCOUNT + "=?", selectionArgs, null, null, null); while(cursor.moveToNext()) { try { - prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)), Base64.DEFAULT))); + prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT))); } catch (IOException ignored) { } } @@ -767,18 +768,18 @@ public class DatabaseBackend extends SQLiteOpenHelper { 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, Base64.encodeToString(record.serialize(),Base64.DEFAULT)); - values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); - db.insert(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values); + values.put(SQLiteAxolotlStore.ID, record.getId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(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 + "=?", + db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", args); } @@ -792,24 +793,24 @@ public class DatabaseBackend extends SQLiteOpenHelper { private Cursor getIdentityKeyCursor(Account account, String name, Boolean own, String fingerprint) { final SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {AxolotlService.SQLiteAxolotlStore.TRUSTED, - AxolotlService.SQLiteAxolotlStore.KEY}; + String[] columns = {SQLiteAxolotlStore.TRUSTED, + SQLiteAxolotlStore.KEY}; ArrayList selectionArgs = new ArrayList<>(4); selectionArgs.add(account.getUuid()); - String selectionString = AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?"; + String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?"; if (name != null){ selectionArgs.add(name); - selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.NAME + " = ?"; + selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?"; } if (fingerprint != null){ selectionArgs.add(fingerprint); - selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ?"; + selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?"; } if (own != null){ selectionArgs.add(own?"1":"0"); - selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.OWN + " = ?"; + selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?"; } - Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, + Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, columns, selectionString, selectionArgs.toArray(new String[selectionArgs.size()]), @@ -824,7 +825,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { if(cursor.getCount() != 0) { cursor.moveToFirst(); try { - identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); + identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); } catch (InvalidKeyException e) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); } @@ -838,18 +839,18 @@ public class DatabaseBackend extends SQLiteOpenHelper { return loadIdentityKeys(account, name, null); } - public Set loadIdentityKeys(Account account, String name, AxolotlService.SQLiteAxolotlStore.Trust trust) { + public Set loadIdentityKeys(Account account, String name, 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)) + cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED)) != trust.getCode()) { continue; } try { - identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0)); + identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0)); } catch (InvalidKeyException e) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account"+account.getJid().toBareJid()+", address: "+name); } @@ -864,55 +865,55 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] args = { account.getUuid(), name, - String.valueOf(AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED.getCode()) + String.valueOf(SQLiteAxolotlStore.Trust.TRUSTED.getCode()) }; - return DatabaseUtils.queryNumEntries(db, AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?" - + " AND " + AxolotlService.SQLiteAxolotlStore.NAME + " = ?" - + " AND " + AxolotlService.SQLiteAxolotlStore.TRUSTED + " = ?", + return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?" + + " AND " + SQLiteAxolotlStore.NAME + " = ?" + + " AND " + SQLiteAxolotlStore.TRUSTED + " = ?", args ); } private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) { - storeIdentityKey(account, name, own, fingerprint, base64Serialized, AxolotlService.SQLiteAxolotlStore.Trust.UNDECIDED); + storeIdentityKey(account, name, own, fingerprint, base64Serialized, SQLiteAxolotlStore.Trust.UNDECIDED); } - private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, AxolotlService.SQLiteAxolotlStore.Trust trusted) { + private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, SQLiteAxolotlStore.Trust trusted) { 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.FINGERPRINT, fingerprint); - values.put(AxolotlService.SQLiteAxolotlStore.KEY, base64Serialized); - values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trusted.getCode()); - db.insert(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + values.put(SQLiteAxolotlStore.NAME, name); + values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0); + values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint); + values.put(SQLiteAxolotlStore.KEY, base64Serialized); + values.put(SQLiteAxolotlStore.TRUSTED, trusted.getCode()); + db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); } - public AxolotlService.SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String fingerprint) { + public SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String fingerprint) { Cursor cursor = getIdentityKeyCursor(account, fingerprint); - AxolotlService.SQLiteAxolotlStore.Trust trust = null; + SQLiteAxolotlStore.Trust trust = null; if (cursor.getCount() > 0) { cursor.moveToFirst(); - int trustValue = cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED)); - trust = AxolotlService.SQLiteAxolotlStore.Trust.fromCode(trustValue); + int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED)); + trust = SQLiteAxolotlStore.Trust.fromCode(trustValue); } cursor.close(); return trust; } - public boolean setIdentityKeyTrust(Account account, String fingerprint, AxolotlService.SQLiteAxolotlStore.Trust trust) { + public boolean setIdentityKeyTrust(Account account, String fingerprint, SQLiteAxolotlStore.Trust trust) { SQLiteDatabase db = this.getWritableDatabase(); String[] selectionArgs = { account.getUuid(), fingerprint }; ContentValues values = new ContentValues(); - values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trust.getCode()); - int rows = db.update(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ", + values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode()); + int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.FINGERPRINT + " = ? ", selectionArgs); return rows == 1; } @@ -922,7 +923,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), AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED); + storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), SQLiteAxolotlStore.Trust.TRUSTED); } public void recreateAxolotlDb() { @@ -931,13 +932,13 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void recreateAxolotlDb(SQLiteDatabase db) { Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+">>> (RE)CREATING AXOLOTL DATABASE <<<"); - db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME); db.execSQL(CREATE_SESSIONS_STATEMENT); - db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME); db.execSQL(CREATE_PREKEYS_STATEMENT); - db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME); db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); - db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME); db.execSQL(CREATE_IDENTITIES_STATEMENT); } @@ -948,17 +949,17 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] deleteArgs= { accountName }; - db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", deleteArgs); - db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", + db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", deleteArgs); - db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", + db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", deleteArgs); - db.delete(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", + db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", deleteArgs); } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 92b8e544..515e814b 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -38,7 +38,7 @@ import de.timroes.android.listview.EnhancedListView; 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.crypto.axolotl.SQLiteAxolotlStore.Trust; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Contact; diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index 1bf07f3e..0b5d092b 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -16,7 +16,7 @@ import java.util.Map; import java.util.Set; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.Trust; +import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore.Trust; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 83568510..52c2e4f4 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -70,7 +70,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.crypto.axolotl.SQLiteAxolotlStore; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -610,16 +610,16 @@ public abstract class XmppActivity extends Activity { protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) { final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); - final AxolotlService.SQLiteAxolotlStore.Trust trust = account.getAxolotlService() + final SQLiteAxolotlStore.Trust trust = account.getAxolotlService() .getFingerprintTrust(fingerprint); return addFingerprintRowWithListeners(keys, account, identityKey, trust, true, new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked != (trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED)) { + if (isChecked != (trust == SQLiteAxolotlStore.Trust.TRUSTED)) { account.getAxolotlService().setFingerprintTrust(fingerprint, - (isChecked) ? AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED : - AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED); + (isChecked) ? SQLiteAxolotlStore.Trust.TRUSTED : + SQLiteAxolotlStore.Trust.UNTRUSTED); } refreshUi(); xmppConnectionService.updateAccountUi(); @@ -630,7 +630,7 @@ public abstract class XmppActivity extends Activity { @Override public void onClick(View v) { account.getAxolotlService().setFingerprintTrust(fingerprint, - AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED); + SQLiteAxolotlStore.Trust.UNTRUSTED); refreshUi(); xmppConnectionService.updateAccountUi(); xmppConnectionService.updateConversationUi(); @@ -642,12 +642,12 @@ public abstract class XmppActivity extends Activity { protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account, final IdentityKey identityKey, - AxolotlService.SQLiteAxolotlStore.Trust trust, + SQLiteAxolotlStore.Trust trust, boolean showTag, CompoundButton.OnCheckedChangeListener onCheckedChangeListener, View.OnClickListener onClickListener) { - if (trust == AxolotlService.SQLiteAxolotlStore.Trust.COMPROMISED) { + if (trust == SQLiteAxolotlStore.Trust.COMPROMISED) { return false; } View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false); @@ -668,7 +668,7 @@ public abstract class XmppActivity extends Activity { switch (trust) { case UNTRUSTED: case TRUSTED: - trustToggle.setChecked(trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED, false); + trustToggle.setChecked(trust == SQLiteAxolotlStore.Trust.TRUSTED, false); trustToggle.setEnabled(true); key.setTextColor(getPrimaryTextColor()); keyType.setTextColor(getSecondaryTextColor()); 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 b158f0fe..f55094af 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -27,7 +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.crypto.axolotl.SQLiteAxolotlStore; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -173,11 +173,11 @@ public class MessageAdapter extends ArrayAdapter { } else { viewHolder.indicator.setVisibility(View.VISIBLE); if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { - AxolotlService.SQLiteAxolotlStore.Trust trust = message.getConversation() + SQLiteAxolotlStore.Trust trust = message.getConversation() .getAccount().getAxolotlService().getFingerprintTrust( message.getAxolotlFingerprint()); - if(trust == null || trust != AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED) { + if(trust == null || trust != SQLiteAxolotlStore.Trust.TRUSTED) { viewHolder.indicator.setColorFilter(Color.RED); } else { viewHolder.indicator.clearColorFilter(); -- cgit v1.2.3 From a3991d59c908c151fa2b1f6ea9f865313d7c8cce Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Tue, 28 Jul 2015 22:29:19 +0200 Subject: Improve trust toggle responsiveness Removed unnecessary UI refreshes, explicitly update UI where needed. --- src/main/java/eu/siacs/conversations/ui/XmppActivity.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 52c2e4f4..857a1dc4 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -621,9 +621,6 @@ public abstract class XmppActivity extends Activity { (isChecked) ? SQLiteAxolotlStore.Trust.TRUSTED : SQLiteAxolotlStore.Trust.UNTRUSTED); } - refreshUi(); - xmppConnectionService.updateAccountUi(); - xmppConnectionService.updateConversationUi(); } }, new View.OnClickListener() { @@ -631,9 +628,7 @@ public abstract class XmppActivity extends Activity { public void onClick(View v) { account.getAxolotlService().setFingerprintTrust(fingerprint, SQLiteAxolotlStore.Trust.UNTRUSTED); - refreshUi(); - xmppConnectionService.updateAccountUi(); - xmppConnectionService.updateConversationUi(); + v.setEnabled(true); } } -- cgit v1.2.3 From 77920c7aa608bd92391ade653eb8971b977eb7d5 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 29 Jul 2015 02:49:14 +0200 Subject: Color plaintext messages in encrypted sessions red Plaintext messages that were received while in an encrypted session are now colored red. We define "in an encrypted session" if a) the last message sent by our own device before the message under consideration (or any message received between then and now) was encrypted AND b) the next message will be sent encrypted or the next message sent after the one under consideration was sent encrypted --- .../conversations/ui/adapter/MessageAdapter.java | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'src/main/java/eu') 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 f55094af..c44796ba 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -560,6 +560,36 @@ public class MessageAdapter extends ArrayAdapter { } } + if (type == RECEIVED) { + boolean wasEncrypted = false; + for (Message iterator = message.prev(); iterator != null; iterator = iterator.prev()){ + if (iterator.getEncryption() != Message.ENCRYPTION_NONE) { + wasEncrypted = true; + break; + } + if (iterator.getRemoteMsgId() == null && iterator.getType() == SENT) { + break; + } + } + boolean willBeEncrypted = conversation.getNextEncryption(false) != Message.ENCRYPTION_NONE; + for (Message iterator = message.next(); iterator != null; iterator = iterator.next()){ + if (iterator.getEncryption() != Message.ENCRYPTION_NONE) { + willBeEncrypted = true; + break; + } + if (iterator.getRemoteMsgId() == null && iterator.getType() == SENT) { + break; + } + } + + if ( willBeEncrypted && wasEncrypted + && message.getEncryption() == Message.ENCRYPTION_NONE) { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); + } else { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received); + } + } + displayStatus(viewHolder, message); return view; -- cgit v1.2.3 From e10a6c5b87d5d4b4871fdf31bbc9a7ef8b8d6ff1 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 29 Jul 2015 02:56:47 +0200 Subject: Fix NPE: consider unknown keys UNDECIDED --- .../java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index b8d7dcba..d58b2dd8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -64,7 +64,8 @@ public class XmppAxolotlSession { } protected SQLiteAxolotlStore.Trust getTrust() { - return sqLiteAxolotlStore.getFingerprintTrust(fingerprint); + SQLiteAxolotlStore.Trust trust = sqLiteAxolotlStore.getFingerprintTrust(fingerprint); + return (trust == null)? SQLiteAxolotlStore.Trust.UNDECIDED : trust; } @Nullable -- cgit v1.2.3 From e6df4d81d23e4b7c0c3798f114714dd3caebfe8c Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 29 Jul 2015 16:41:58 +0200 Subject: Tag carbon messages in parser, adapt session logic Messages sent from another device of the own account are now explicitly tagged as carboned message. The session detection logic now uses this tag to find "session borders". --- .../eu/siacs/conversations/entities/Message.java | 24 +++++++++++++++++++--- .../siacs/conversations/parser/MessageParser.java | 5 ++++- .../conversations/persistance/DatabaseBackend.java | 6 +++++- .../conversations/ui/adapter/MessageAdapter.java | 4 ++-- 4 files changed, 32 insertions(+), 7 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 14f96296..99b78d14 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -51,6 +51,7 @@ public class Message extends AbstractEntity { public static final String ENCRYPTION = "encryption"; public static final String STATUS = "status"; public static final String TYPE = "type"; + public static final String CARBON = "carbon"; public static final String REMOTE_MSG_ID = "remoteMsgId"; public static final String SERVER_MSG_ID = "serverMsgId"; public static final String RELATIVE_FILE_PATH = "relativeFilePath"; @@ -68,6 +69,7 @@ public class Message extends AbstractEntity { protected int encryption; protected int status; protected int type; + protected boolean carbon = false; protected String relativeFilePath; protected boolean read = true; protected String remoteMsgId = null; @@ -85,8 +87,11 @@ public class Message extends AbstractEntity { public Message(Conversation conversation, String body, int encryption) { this(conversation, body, encryption, STATUS_UNSEND); } - public Message(Conversation conversation, String body, int encryption, int status) { + this(conversation, body, encryption, status, false); + } + + public Message(Conversation conversation, String body, int encryption, int status, boolean carbon) { this(java.util.UUID.randomUUID().toString(), conversation.getUuid(), conversation.getJid() == null ? null : conversation.getJid().toBareJid(), @@ -96,6 +101,7 @@ public class Message extends AbstractEntity { encryption, status, TYPE_TEXT, + false, null, null, null, @@ -105,8 +111,9 @@ 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 fingerprint) { + final int encryption, final int status, final int type, final boolean carbon, + final String remoteMsgId, final String relativeFilePath, + final String serverMsgId, final String fingerprint) { this.uuid = uuid; this.conversationUuid = conversationUUid; this.counterpart = counterpart; @@ -116,6 +123,7 @@ public class Message extends AbstractEntity { this.encryption = encryption; this.status = status; this.type = type; + this.carbon = carbon; this.remoteMsgId = remoteMsgId; this.relativeFilePath = relativeFilePath; this.serverMsgId = serverMsgId; @@ -154,6 +162,7 @@ public class Message extends AbstractEntity { cursor.getInt(cursor.getColumnIndex(ENCRYPTION)), cursor.getInt(cursor.getColumnIndex(STATUS)), cursor.getInt(cursor.getColumnIndex(TYPE)), + cursor.getInt(cursor.getColumnIndex(CARBON))>0, cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)), cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)), cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)), @@ -188,6 +197,7 @@ public class Message extends AbstractEntity { values.put(ENCRYPTION, encryption); values.put(STATUS, status); values.put(TYPE, type); + values.put(CARBON, carbon ? 1 : 0); values.put(REMOTE_MSG_ID, remoteMsgId); values.put(RELATIVE_FILE_PATH, relativeFilePath); values.put(SERVER_MSG_ID, serverMsgId); @@ -312,6 +322,14 @@ public class Message extends AbstractEntity { this.type = type; } + public boolean isCarbon() { + return carbon; + } + + public void setCarbon(boolean carbon) { + this.carbon = carbon; + } + public void setTrueCounterpart(Jid trueCounterpart) { this.trueCounterpart = trueCounterpart; } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index f0659511..3aaa3ce7 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -230,6 +230,7 @@ public class MessageParser extends AbstractParser implements final MessagePacket packet; Long timestamp = null; final boolean isForwarded; + boolean isCarbon = false; String serverMsgId = null; final Element fin = original.findChild("fin", "urn:xmpp:mam:0"); if (fin != null) { @@ -260,7 +261,8 @@ public class MessageParser extends AbstractParser implements return; } timestamp = f != null ? f.second : null; - isForwarded = f != null; + isCarbon = f != null; + isForwarded = isCarbon; } else { packet = original; isForwarded = false; @@ -346,6 +348,7 @@ public class MessageParser extends AbstractParser implements message.setCounterpart(counterpart); message.setRemoteMsgId(remoteMsgId); message.setServerMsgId(serverMsgId); + message.setCarbon(isCarbon); message.setTime(timestamp); message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0"); if (conversation.getMode() == Conversation.MODE_MULTI) { diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 99dbcf34..b3fce845 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -41,7 +41,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 15; + private static final int DATABASE_VERSION = 16; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -295,6 +295,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.FINGERPRINT + " TEXT"); } + if (oldVersion < 16 && newVersion >= 16) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.CARBON + " INTEGER"); + } } public static synchronized DatabaseBackend getInstance(Context context) { 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 c44796ba..c4b1e6fc 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -567,7 +567,7 @@ public class MessageAdapter extends ArrayAdapter { wasEncrypted = true; break; } - if (iterator.getRemoteMsgId() == null && iterator.getType() == SENT) { + if (!iterator.isCarbon() && iterator.getType() == SENT) { break; } } @@ -577,7 +577,7 @@ public class MessageAdapter extends ArrayAdapter { willBeEncrypted = true; break; } - if (iterator.getRemoteMsgId() == null && iterator.getType() == SENT) { + if (!iterator.isCarbon() && iterator.getType() == SENT) { break; } } -- cgit v1.2.3 From e07853ea6267304b5380e77b794a3f906b06aca4 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 29 Jul 2015 20:10:21 +0200 Subject: Rerender message bubbles on encryption change --- src/main/java/eu/siacs/conversations/ui/ConversationActivity.java | 1 + 1 file changed, 1 insertion(+) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 515e814b..147ae824 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -775,6 +775,7 @@ public class ConversationActivity extends XmppActivity xmppConnectionService.databaseBackend.updateConversation(conversation); fragment.updateChatMsgHint(); invalidateOptionsMenu(); + refreshUi(); return true; } }); -- cgit v1.2.3 From 2b3bb0226121acb173080e4893421eefef4a9571 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 29 Jul 2015 20:21:37 +0200 Subject: Highlight selected message's fingerprint in list --- .../conversations/ui/ContactDetailsActivity.java | 5 +++- .../conversations/ui/ConversationFragment.java | 3 ++- .../conversations/ui/EditAccountActivity.java | 5 +++- .../siacs/conversations/ui/TrustKeysActivity.java | 4 ++-- .../eu/siacs/conversations/ui/XmppActivity.java | 28 +++++++++++++++------- 5 files changed, 32 insertions(+), 13 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index a0e02c1b..4c3923bb 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -110,6 +110,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd private LinearLayout keys; private LinearLayout tags; private boolean showDynamicTags; + private String messageFingerprint; private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() { @@ -192,6 +193,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } catch (final InvalidJidException ignored) { } } + this.messageFingerprint = getIntent().getStringExtra("fingerprint"); setContentView(R.layout.activity_contact_details); contactJidTv = (TextView) findViewById(R.id.details_contactjid); @@ -385,7 +387,8 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys( contact.getAccount(), contact.getJid().toBareJid().toString())) { - hasKeys |= addFingerprintRow(keys, contact.getAccount(), identityKey); + boolean highlight = identityKey.getFingerprint().replaceAll("\\s", "").equals(messageFingerprint); + hasKeys |= addFingerprintRow(keys, contact.getAccount(), identityKey, highlight); } if (contact.getPgpKeyId() != 0) { hasKeys = true; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 04429421..47c6a764 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -392,12 +392,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa highlightInConference(user); } } else { - activity.switchToContactDetails(message.getContact()); + activity.switchToContactDetails(message.getContact(), message.getAxolotlFingerprint()); } } else { Account account = message.getConversation().getAccount(); Intent intent = new Intent(activity, EditAccountActivity.class); intent.putExtra("jid", account.getJid().toBareJid().toString()); + intent.putExtra("fingerprint", message.getAxolotlFingerprint()); startActivity(intent); } } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 3c1f155d..a1c28df8 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -74,6 +74,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private Jid jidToEdit; private Account mAccount; + private String messageFingerprint; private boolean mFetchingAvatar = false; @@ -388,6 +389,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } catch (final InvalidJidException | NullPointerException ignored) { this.jidToEdit = null; } + this.messageFingerprint = getIntent().getStringExtra("fingerprint"); if (this.jidToEdit != null) { this.mRegisterNew.setVisibility(View.GONE); if (getActionBar() != null) { @@ -571,7 +573,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate if(ownKey.equals(identityKey)) { continue; } - hasKeys |= addFingerprintRow(keys, mAccount, identityKey); + boolean highlight = identityKey.getFingerprint().replaceAll("\\s", "").equals(messageFingerprint); + hasKeys |= addFingerprintRow(keys, mAccount, identityKey, highlight); } if (hasKeys) { keysCard.setVisibility(View.VISIBLE); diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index 0b5d092b..d5959b7a 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -118,7 +118,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate boolean hasForeignKeys = false; for(final IdentityKey identityKey : ownKeysToTrust.keySet()) { hasOwnKeys = true; - addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey, + addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey, false, Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false, new CompoundButton.OnCheckedChangeListener() { @Override @@ -134,7 +134,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate } for(final IdentityKey identityKey : foreignKeysToTrust.keySet()) { hasForeignKeys = true; - addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey, + addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey, false, Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false, new CompoundButton.OnCheckedChangeListener() { @Override diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 857a1dc4..3093db46 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -424,10 +424,15 @@ public abstract class XmppActivity extends Activity { } public void switchToContactDetails(Contact contact) { + switchToContactDetails(contact, null); + } + + public void switchToContactDetails(Contact contact, String messageFingerprint) { Intent intent = new Intent(this, ContactDetailsActivity.class); intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); intent.putExtra("account", contact.getAccount().getJid().toBareJid().toString()); intent.putExtra("contact", contact.getJid().toString()); + intent.putExtra("fingerprint", messageFingerprint); startActivity(intent); } @@ -608,11 +613,11 @@ public abstract class XmppActivity extends Activity { builder.create().show(); } - protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) { + protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey, boolean highlight) { final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); final SQLiteAxolotlStore.Trust trust = account.getAxolotlService() .getFingerprintTrust(fingerprint); - return addFingerprintRowWithListeners(keys, account, identityKey, trust, true, + return addFingerprintRowWithListeners(keys, account, identityKey, highlight, trust, true, new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @@ -636,12 +641,13 @@ public abstract class XmppActivity extends Activity { } protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account, - final IdentityKey identityKey, - SQLiteAxolotlStore.Trust trust, - boolean showTag, - CompoundButton.OnCheckedChangeListener - onCheckedChangeListener, - View.OnClickListener onClickListener) { + final IdentityKey identityKey, + boolean highlight, + SQLiteAxolotlStore.Trust trust, + boolean showTag, + CompoundButton.OnCheckedChangeListener + onCheckedChangeListener, + View.OnClickListener onClickListener) { if (trust == SQLiteAxolotlStore.Trust.COMPROMISED) { return false; } @@ -688,6 +694,12 @@ public abstract class XmppActivity extends Activity { } else { keyType.setVisibility(View.GONE); } + if (highlight) { + keyType.setTextColor(getResources().getColor(R.color.accent)); + keyType.setText(getString(R.string.axolotl_fingerprint_selected_message)); + } else { + keyType.setText(getString(R.string.axolotl_fingerprint)); + } key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint())); keys.addView(view); -- cgit v1.2.3 From b7c64cd19d011ad24d64e13ab9154ae07f06a872 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 29 Jul 2015 20:25:14 +0200 Subject: Reset lock color for non-axolotl messages --- src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/main/java/eu') 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 c4b1e6fc..2bd89ccb 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -182,6 +182,8 @@ public class MessageAdapter extends ArrayAdapter { } else { viewHolder.indicator.clearColorFilter(); } + } else { + viewHolder.indicator.clearColorFilter(); } } -- cgit v1.2.3 From 58d80f58be2f75b858e93766c61857a78d161005 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 29 Jul 2015 23:45:37 +0200 Subject: use gcm for file encryption over http --- .../conversations/entities/DownloadableFile.java | 97 ++-------------------- .../conversations/http/HttpDownloadConnection.java | 21 +++-- .../conversations/http/HttpUploadConnection.java | 32 +++++-- .../xmpp/jingle/JingleInbandTransport.java | 4 +- .../xmpp/jingle/JingleSocks5Transport.java | 4 +- .../conversations/xmpp/jingle/JingleTransport.java | 73 ++++++++++++++++ 6 files changed, 127 insertions(+), 104 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java index ca325448..452bf815 100644 --- a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java +++ b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java @@ -1,26 +1,7 @@ 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.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -import eu.siacs.conversations.Config; + import eu.siacs.conversations.utils.MimeUtils; public class DownloadableFile extends File { @@ -29,8 +10,7 @@ public class DownloadableFile extends File { private long expectedSize = 0; private String sha1sum; - private Key aeskey; - private String mime; + private byte[] aeskey; private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf }; @@ -84,85 +64,24 @@ public class DownloadableFile extends File { byte[] iv = new byte[16]; System.arraycopy(key, 0, iv, 0, 16); System.arraycopy(key, 16, secretKey, 0, 32); - this.aeskey = new SecretKeySpec(secretKey, "AES"); + this.aeskey = secretKey; this.iv = iv; } else if (key.length >= 32) { byte[] secretKey = new byte[32]; System.arraycopy(key, 0, secretKey, 0, 32); - this.aeskey = new SecretKeySpec(secretKey, "AES"); + this.aeskey = secretKey; } else if (key.length >= 16) { byte[] secretKey = new byte[16]; System.arraycopy(key, 0, secretKey, 0, 16); - this.aeskey = new SecretKeySpec(secretKey, "AES"); + this.aeskey = secretKey; } } - public Key getKey() { + public byte[] getKey() { return this.aeskey; } - public InputStream createInputStream() { - if (this.getKey() == null) { - try { - return new FileInputStream(this); - } catch (FileNotFoundException e) { - return null; - } - } else { - try { - IvParameterSpec ips = new IvParameterSpec(iv); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, this.getKey(), ips); - Log.d(Config.LOGTAG, "opening encrypted input stream"); - return new CipherInputStream(new FileInputStream(this), cipher); - } catch (NoSuchAlgorithmException e) { - Log.d(Config.LOGTAG, "no such algo: " + e.getMessage()); - return null; - } catch (NoSuchPaddingException e) { - Log.d(Config.LOGTAG, "no such padding: " + e.getMessage()); - return null; - } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, "invalid key: " + e.getMessage()); - return null; - } catch (InvalidAlgorithmParameterException e) { - Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); - return null; - } catch (FileNotFoundException e) { - return null; - } - } - } - - public OutputStream createOutputStream() { - if (this.getKey() == null) { - try { - return new FileOutputStream(this); - } catch (FileNotFoundException e) { - return null; - } - } else { - try { - IvParameterSpec ips = new IvParameterSpec(this.iv); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.DECRYPT_MODE, this.getKey(), ips); - Log.d(Config.LOGTAG, "opening encrypted output stream"); - return new CipherOutputStream(new FileOutputStream(this), - cipher); - } catch (NoSuchAlgorithmException e) { - Log.d(Config.LOGTAG, "no such algo: " + e.getMessage()); - return null; - } catch (NoSuchPaddingException e) { - Log.d(Config.LOGTAG, "no such padding: " + e.getMessage()); - return null; - } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, "invalid key: " + e.getMessage()); - return null; - } catch (InvalidAlgorithmParameterException e) { - Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); - return null; - } catch (FileNotFoundException e) { - return null; - } - } + public byte[] getIv() { + return this.iv; } } diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 30d9a393..51479836 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -2,10 +2,17 @@ package eu.siacs.conversations.http; import android.content.Intent; import android.net.Uri; -import android.os.SystemClock; import android.util.Log; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.io.CipherOutputStream; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; + import java.io.BufferedInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; @@ -206,7 +213,7 @@ public class HttpDownloadConnection implements Transferable { } } - private void download() throws SSLHandshakeException, IOException { + private void download() throws IOException { HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); if (connection instanceof HttpsURLConnection) { mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); @@ -215,9 +222,13 @@ public class HttpDownloadConnection implements Transferable { BufferedInputStream is = new BufferedInputStream(connection.getInputStream()); file.getParentFile().mkdirs(); file.createNewFile(); - OutputStream os = file.createOutputStream(); - if (os == null) { - throw new IOException(); + OutputStream os; + if (file.getKey() != null) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + os = new CipherOutputStream(new FileOutputStream(file), cipher); + } else { + os = new FileOutputStream(file); } long transmitted = 0; long expected = file.getExpectedSize(); diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index c9fb3d8c..70dcc642 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -1,8 +1,18 @@ package eu.siacs.conversations.http; import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; import android.util.Log; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.io.CipherInputStream; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; + +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -43,7 +53,7 @@ public class HttpUploadConnection implements Transferable { private byte[] key = null; private long transmitted = 0; - private long expected = 1; + private int expected = 1; public HttpUploadConnection(HttpConnectionManager httpConnectionManager) { this.mHttpConnectionManager = httpConnectionManager; @@ -142,14 +152,21 @@ public class HttpUploadConnection implements Transferable { if (connection instanceof HttpsURLConnection) { mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); } + if (file.getKey() != null) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + expected = cipher.getOutputSize((int) file.getSize()); + is = new CipherInputStream(new FileInputStream(file), cipher); + } else { + expected = (int) file.getSize(); + is = new FileInputStream(file); + } connection.setRequestMethod("PUT"); - connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); + connection.setFixedLengthStreamingMode(expected); connection.setDoOutput(true); connection.connect(); os = connection.getOutputStream(); - is = file.createInputStream(); transmitted = 0; - expected = file.getExpectedSize(); int count = -1; byte[] buffer = new byte[4096]; while (((count = is.read(buffer)) != -1) && !canceled) { @@ -163,11 +180,13 @@ public class HttpUploadConnection implements Transferable { int code = connection.getResponseCode(); if (code == 200 || code == 201) { Log.d(Config.LOGTAG, "finished uploading file"); - Message.FileParams params = message.getFileParams(); if (key != null) { mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key)); } mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl); + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + intent.setData(Uri.fromFile(file)); + mXmppConnectionService.sendBroadcast(intent); message.setTransferable(null); message.setCounterpart(message.getConversation().getJid().toBareJid()); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { @@ -188,12 +207,13 @@ public class HttpUploadConnection implements Transferable { } }); } else { - mXmppConnectionService.resendMessage(message,delayed); + mXmppConnectionService.resendMessage(message, delayed); } } else { fail(); } } catch (IOException e) { + e.printStackTrace(); Log.d(Config.LOGTAG,"http upload failed "+e.getMessage()); fail(); } finally { 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 6f31f163..11055b8f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java @@ -93,7 +93,7 @@ public class JingleInbandTransport extends JingleTransport { digest.reset(); file.getParentFile().mkdirs(); file.createNewFile(); - this.fileOutputStream = file.createOutputStream(); + this.fileOutputStream = createOutputStream(file); if (this.fileOutputStream == null) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream"); callback.onFileTransferAborted(); @@ -120,7 +120,7 @@ public class JingleInbandTransport extends JingleTransport { this.fileSize = this.remainingSize; this.digest = MessageDigest.getInstance("SHA-1"); this.digest.reset(); - fileInputStream = this.file.createInputStream(); + fileInputStream = createInputStream(this.file); if (fileInputStream == null) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream"); callback.onFileTransferAborted(); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java index 8d74f44e..55039070 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -106,7 +106,7 @@ public class JingleSocks5Transport extends JingleTransport { try { MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.reset(); - fileInputStream = file.createInputStream(); + fileInputStream = createInputStream(file); //file.createInputStream(); if (fileInputStream == null) { Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream"); callback.onFileTransferAborted(); @@ -157,7 +157,7 @@ public class JingleSocks5Transport extends JingleTransport { socket.setSoTimeout(30000); file.getParentFile().mkdirs(); file.createNewFile(); - fileOutputStream = file.createOutputStream(); + fileOutputStream = createOutputStream(file); if (fileOutputStream == null) { callback.onFileTransferAborted(); Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream"); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java index e832d3f5..1219794f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java @@ -1,5 +1,24 @@ package eu.siacs.conversations.xmpp.jingle; +import android.util.Log; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.DownloadableFile; public abstract class JingleTransport { @@ -12,4 +31,58 @@ public abstract class JingleTransport { final OnFileTransmissionStatusChanged callback); public abstract void disconnect(); + + protected InputStream createInputStream(DownloadableFile file) { + FileInputStream is; + try { + is = new FileInputStream(file); + if (file.getKey() == null) { + return is; + } + } catch (FileNotFoundException e) { + return null; + } + try { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted input stream"); + return new CipherInputStream(is, cipher); + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } + + protected OutputStream createOutputStream(DownloadableFile file) { + FileOutputStream os; + try { + os = new FileOutputStream(file); + if (file.getKey() == null) { + return os; + } + } catch (FileNotFoundException e) { + return null; + } + try { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted output stream"); + return new CipherOutputStream(os, cipher); + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } } -- cgit v1.2.3 From 1ed550b5c54ff873375c30fd94e4f20523f15187 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 Jul 2015 12:40:50 +0200 Subject: fixed some colors in message adapter --- .../eu/siacs/conversations/ui/XmppActivity.java | 2 +- .../conversations/ui/adapter/MessageAdapter.java | 56 ++++++++++++---------- 2 files changed, 32 insertions(+), 26 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 3093db46..64ead283 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -351,7 +351,7 @@ public abstract class XmppActivity extends Activity { mPrimaryTextColor = getResources().getColor(R.color.black87); mSecondaryTextColor = getResources().getColor(R.color.black54); mTertiaryTextColor = getResources().getColor(R.color.black12); - mColorRed = getResources().getColor(R.color.red500); + mColorRed = getResources().getColor(R.color.red800); mColorOrange = getResources().getColor(R.color.orange500); mColorGreen = getResources().getColor(R.color.green500); mPrimaryColor = getResources().getColor(R.color.green500); 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 2bd89ccb..4d1260e8 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -96,19 +96,15 @@ public class MessageAdapter extends ArrayAdapter { return this.getItemViewType(getItem(position)); } - private int getMessageTextColor(Message message) { - int type = this.getItemViewType(message); - + private int getMessageTextColor(int type, boolean primary) { if (type == SENT) { - return activity.getResources().getColor(R.color.black87); - } else if (type == RECEIVED) { - return activity.getResources().getColor(R.color.white); + return activity.getResources().getColor(primary ? R.color.black87 : R.color.black54); + } else { + return activity.getResources().getColor(primary ? R.color.white : R.color.white70); } - - return activity.getPrimaryTextColor(); } - private void displayStatus(ViewHolder viewHolder, Message message) { + private void displayStatus(ViewHolder viewHolder, Message message, int type) { String filesize = null; String info = null; boolean error = false; @@ -163,10 +159,10 @@ public class MessageAdapter extends ArrayAdapter { } break; } - if (error) { + if (error && type == SENT) { viewHolder.time.setTextColor(activity.getWarningTextColor()); } else { - viewHolder.time.setTextColor(this.getMessageTextColor(message)); + viewHolder.time.setTextColor(this.getMessageTextColor(type,false)); } if (message.getEncryption() == Message.ENCRYPTION_NONE) { viewHolder.indicator.setVisibility(View.GONE); @@ -178,12 +174,23 @@ public class MessageAdapter extends ArrayAdapter { message.getAxolotlFingerprint()); if(trust == null || trust != SQLiteAxolotlStore.Trust.TRUSTED) { - viewHolder.indicator.setColorFilter(Color.RED); + viewHolder.indicator.setColorFilter(activity.getWarningTextColor()); + viewHolder.indicator.setAlpha(1.0f); } else { viewHolder.indicator.clearColorFilter(); + if (type == SENT) { + viewHolder.indicator.setAlpha(0.57f); + } else { + viewHolder.indicator.setAlpha(0.7f); + } } } else { viewHolder.indicator.clearColorFilter(); + if (type == SENT) { + viewHolder.indicator.setAlpha(0.57f); + } else { + viewHolder.indicator.setAlpha(0.7f); + } } } @@ -216,19 +223,19 @@ public class MessageAdapter extends ArrayAdapter { } } - private void displayInfoMessage(ViewHolder viewHolder, String text) { + private void displayInfoMessage(ViewHolder viewHolder, String text, int type) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(text); - viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor()); + viewHolder.messageBody.setTextColor(getMessageTextColor(type,false)); viewHolder.messageBody.setTypeface(null, Typeface.ITALIC); viewHolder.messageBody.setTextIsSelectable(false); } - private void displayDecryptionFailed(ViewHolder viewHolder) { + private void displayDecryptionFailed(ViewHolder viewHolder, int type) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } @@ -236,7 +243,7 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(getContext().getString( R.string.decryption_failed)); - viewHolder.messageBody.setTextColor(activity.getWarningTextColor()); + viewHolder.messageBody.setTextColor(getMessageTextColor(type,false)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setTextIsSelectable(false); } @@ -254,7 +261,7 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.messageBody.setText(span); } - private void displayTextMessage(final ViewHolder viewHolder, final Message message) { + private void displayTextMessage(final ViewHolder viewHolder, final Message message, int type) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } @@ -312,7 +319,7 @@ public class MessageAdapter extends ArrayAdapter { } else { viewHolder.messageBody.setText(""); } - viewHolder.messageBody.setTextColor(this.getMessageTextColor(message)); + viewHolder.messageBody.setTextColor(this.getMessageTextColor(type,true)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setTextIsSelectable(true); } @@ -521,7 +528,7 @@ public class MessageAdapter extends ArrayAdapter { } else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) { displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); } else { - displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first); + displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first,type); } } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { displayImageMessage(viewHolder, message); @@ -533,10 +540,9 @@ public class MessageAdapter extends ArrayAdapter { } } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { if (activity.hasPgp()) { - displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message)); + displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message),type); } else { - displayInfoMessage(viewHolder, - activity.getString(R.string.install_openkeychain)); + displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),type); if (viewHolder != null) { viewHolder.message_box .setOnClickListener(new OnClickListener() { @@ -549,7 +555,7 @@ public class MessageAdapter extends ArrayAdapter { } } } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { - displayDecryptionFailed(viewHolder); + displayDecryptionFailed(viewHolder,type); } else { if (GeoHelper.isGeoUri(message.getBody())) { displayLocationMessage(viewHolder,message); @@ -558,7 +564,7 @@ public class MessageAdapter extends ArrayAdapter { } else if (message.treatAsDownloadable() == Message.Decision.MUST) { displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); } else { - displayTextMessage(viewHolder, message); + displayTextMessage(viewHolder, message, type); } } @@ -592,7 +598,7 @@ public class MessageAdapter extends ArrayAdapter { } } - displayStatus(viewHolder, message); + displayStatus(viewHolder, message, type); return view; } -- cgit v1.2.3 From 74ab36fda2d977e1b97b755a8298c488e6d74ba2 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Thu, 30 Jul 2015 19:16:58 +0200 Subject: Fix session logic: enforce same type of encryption --- .../eu/siacs/conversations/entities/Message.java | 31 ++++++++++++++++++++++ .../conversations/ui/adapter/MessageAdapter.java | 28 +++---------------- 2 files changed, 34 insertions(+), 25 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 99b78d14..e6687701 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -709,4 +709,35 @@ public class Message extends AbstractEntity { return conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint) == SQLiteAxolotlStore.Trust.TRUSTED; } + + private int getPreviousEncryption() { + for (Message iterator = this.prev(); iterator != null; iterator = iterator.prev()){ + if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) { + continue; + } + return iterator.getEncryption(); + } + return ENCRYPTION_NONE; + } + + private int getNextEncryption() { + for (Message iterator = this.next(); iterator != null; iterator = iterator.next()){ + if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) { + continue; + } + return iterator.getEncryption(); + } + return conversation.getNextEncryption(false); + } + + public boolean isValidInSession() { + int pastEncryption = this.getPreviousEncryption(); + int futureEncryption = this.getNextEncryption(); + + boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE + || futureEncryption == ENCRYPTION_NONE + || pastEncryption != futureEncryption; + + return inUnencryptedSession || this.getEncryption() == pastEncryption; + } } 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 4d1260e8..76da42c4 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -569,32 +569,10 @@ public class MessageAdapter extends ArrayAdapter { } if (type == RECEIVED) { - boolean wasEncrypted = false; - for (Message iterator = message.prev(); iterator != null; iterator = iterator.prev()){ - if (iterator.getEncryption() != Message.ENCRYPTION_NONE) { - wasEncrypted = true; - break; - } - if (!iterator.isCarbon() && iterator.getType() == SENT) { - break; - } - } - boolean willBeEncrypted = conversation.getNextEncryption(false) != Message.ENCRYPTION_NONE; - for (Message iterator = message.next(); iterator != null; iterator = iterator.next()){ - if (iterator.getEncryption() != Message.ENCRYPTION_NONE) { - willBeEncrypted = true; - break; - } - if (!iterator.isCarbon() && iterator.getType() == SENT) { - break; - } - } - - if ( willBeEncrypted && wasEncrypted - && message.getEncryption() == Message.ENCRYPTION_NONE) { - viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); - } else { + if(message.isValidInSession()) { viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received); + } else { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); } } -- cgit v1.2.3 From 658919f239e9c816f958d17df9ba5ee79b6549b2 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 31 Jul 2015 00:52:46 +0200 Subject: improved 'next encryption' selection --- .../siacs/conversations/entities/Conversation.java | 63 ++++++++++++---------- .../eu/siacs/conversations/entities/Message.java | 2 +- .../siacs/conversations/parser/MessageParser.java | 2 - .../services/XmppConnectionService.java | 20 +++---- .../conversations/ui/ConversationActivity.java | 23 +++----- .../conversations/ui/ConversationFragment.java | 32 ++++++----- 6 files changed, 69 insertions(+), 73 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 6d99e358..9f9f34cf 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -549,42 +549,51 @@ public class Conversation extends AbstractEntity implements Blockable { return this.nextCounterpart; } - public int getLatestEncryption() { - int latestEncryption = this.getLatestMessage().getEncryption(); - if ((latestEncryption == Message.ENCRYPTION_DECRYPTED) - || (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) { - return Message.ENCRYPTION_PGP; - } else { - return latestEncryption; + private int getMostRecentlyUsedOutgoingEncryption() { + synchronized (this.messages) { + for(int i = this.messages.size() -1; i >= 0; --i) { + final Message m = this.messages.get(0); + if (!m.isCarbon() && m.getStatus() != Message.STATUS_RECEIVED) { + final int e = m.getEncryption(); + if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) { + return Message.ENCRYPTION_PGP; + } else { + return e; + } + } + } } + return Message.ENCRYPTION_NONE; } - public int getNextEncryption(boolean force) { - int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1); - if (next == -1) { - int latest = this.getLatestEncryption(); - if (latest == Message.ENCRYPTION_NONE) { - if (force && getMode() == MODE_SINGLE) { - return Message.ENCRYPTION_OTR; - } else if (getContact().getPresences().size() == 1) { - if (getContact().getOtrFingerprints().size() >= 1) { - return Message.ENCRYPTION_OTR; + private int getMostRecentlyUsedIncomingEncryption() { + synchronized (this.messages) { + for(int i = this.messages.size() -1; i >= 0; --i) { + final Message m = this.messages.get(0); + if (m.getStatus() == Message.STATUS_RECEIVED) { + final int e = m.getEncryption(); + if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) { + return Message.ENCRYPTION_PGP; } else { - return latest; + return e; } - } else { - return latest; } - } else { - return latest; } } - if (next == Message.ENCRYPTION_NONE && force - && getMode() == MODE_SINGLE) { - return Message.ENCRYPTION_OTR; - } else { - return next; + return Message.ENCRYPTION_NONE; + } + + public int getNextEncryption() { + int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1); + if (next == -1) { + int outgoing = this.getMostRecentlyUsedOutgoingEncryption(); + if (outgoing == Message.ENCRYPTION_NONE) { + return this.getMostRecentlyUsedIncomingEncryption(); + } else { + return outgoing; + } } + return next; } public void setNextEncryption(int encryption) { diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index e6687701..6c2a1cc0 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -727,7 +727,7 @@ public class Message extends AbstractEntity { } return iterator.getEncryption(); } - return conversation.getNextEncryption(false); + return conversation.getNextEncryption(); } public boolean isValidInSession() { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 3aaa3ce7..4bee34a9 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -73,11 +73,9 @@ public class MessageParser extends AbstractParser implements body = otrSession.transformReceiving(body); SessionStatus status = otrSession.getSessionStatus(); if (body == null && status == SessionStatus.ENCRYPTED) { - conversation.setNextEncryption(Message.ENCRYPTION_OTR); mXmppConnectionService.onOtrSessionEstablished(conversation); return null; } else if (body == null && status == SessionStatus.FINISHED) { - conversation.setNextEncryption(Message.ENCRYPTION_NONE); conversation.resetOtrSession(); mXmppConnectionService.updateConversationUi(); return null; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index cadcf851..13b81090 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -349,7 +349,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void attachLocationToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) { - int encryption = conversation.getNextEncryption(forceEncryption()); + int encryption = conversation.getNextEncryption(); if (encryption == Message.ENCRYPTION_PGP) { encryption = Message.ENCRYPTION_DECRYPTED; } @@ -368,12 +368,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa final Uri uri, final UiCallback callback) { final Message message; - if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) { - message = new Message(conversation, "", - Message.ENCRYPTION_DECRYPTED); + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); } else { - message = new Message(conversation, "", - conversation.getNextEncryption(forceEncryption())); + message = new Message(conversation, "", conversation.getNextEncryption()); } message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_FILE); @@ -409,12 +407,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) { final Message message; - if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) { - message = new Message(conversation, "", - Message.ENCRYPTION_DECRYPTED); + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); } else { - message = new Message(conversation, "", - conversation.getNextEncryption(forceEncryption())); + message = new Message(conversation, "",conversation.getNextEncryption()); } message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_IMAGE); @@ -424,7 +420,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void run() { try { getFileBackend().copyImageToPrivateStorage(message, uri); - if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) { + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { getPgpEngine().encrypt(message, callback); } else { callback.success(message); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 147ae824..daf22122 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -385,7 +385,7 @@ public class ConversationActivity extends XmppActivity } else { menuAdd.setVisible(!isConversationsOverviewHideable()); if (this.getSelectedConversation() != null) { - if (this.getSelectedConversation().getNextEncryption(forceEncryption()) != Message.ENCRYPTION_NONE) { + if (this.getSelectedConversation().getNextEncryption() != Message.ENCRYPTION_NONE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { menuSecure.setIcon(R.drawable.ic_lock_white_24dp); } else { @@ -498,7 +498,7 @@ public class ConversationActivity extends XmppActivity break; } final Conversation conversation = getSelectedConversation(); - final int encryption = conversation.getNextEncryption(forceEncryption()); + final int encryption = conversation.getNextEncryption(); if (encryption == Message.ENCRYPTION_PGP) { if (hasPgp()) { if (conversation.getContact().getPgpKeyId() != 0) { @@ -787,15 +787,10 @@ public class ConversationActivity extends XmppActivity 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())) { + } else if (!conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) { axolotl.setEnabled(false); } - switch (conversation.getNextEncryption(forceEncryption())) { + switch (conversation.getNextEncryption()) { case Message.ENCRYPTION_NONE: none.setChecked(true); break; @@ -806,8 +801,7 @@ public class ConversationActivity extends XmppActivity pgp.setChecked(true); break; case Message.ENCRYPTION_AXOLOTL: - popup.getMenu().findItem(R.id.encryption_choice_axolotl) - .setChecked(true); + axolotl.setChecked(true); break; default: none.setChecked(true); @@ -820,8 +814,7 @@ public class ConversationActivity extends XmppActivity protected void muteConversationDialog(final Conversation conversation) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.disable_notifications); - final int[] durations = getResources().getIntArray( - R.array.mute_options_durations); + final int[] durations = getResources().getIntArray(R.array.mute_options_durations); builder.setItems(R.array.mute_options_descriptions, new OnClickListener() { @@ -1253,10 +1246,6 @@ public class ConversationActivity extends XmppActivity }); } - public boolean forceEncryption() { - return getPreferences().getBoolean("force_encryption", false); - } - public boolean useSendButtonToIndicateStatus() { return getPreferences().getBoolean("send_button_status", false); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 47c6a764..88504a92 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -293,23 +293,27 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (body.length() == 0 || this.conversation == null) { return; } - Message message = new Message(conversation, body, conversation.getNextEncryption(activity.forceEncryption())); + Message message = new Message(conversation, body, conversation.getNextEncryption()); if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getNextCounterpart() != null) { message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_PRIVATE); } } - if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_OTR) { - sendOtrMessage(message); - } else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_PGP) { - sendPgpMessage(message); - } else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_AXOLOTL) { - if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) { - sendAxolotlMessage(message); - } - } else { - sendPlainTextMessage(message); + switch (conversation.getNextEncryption()) { + case Message.ENCRYPTION_OTR: + sendOtrMessage(message); + break; + case Message.ENCRYPTION_PGP: + sendPgpMessage(message); + break; + case Message.ENCRYPTION_AXOLOTL: + if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) { + sendAxolotlMessage(message); + } + break; + default: + sendPlainTextMessage(message); } } @@ -320,7 +324,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa R.string.send_private_message_to, conversation.getNextCounterpart().getResourcepart())); } else { - switch (conversation.getNextEncryption(activity.forceEncryption())) { + switch (conversation.getNextEncryption()) { case Message.ENCRYPTION_NONE: mEditMessage .setHint(getString(R.string.send_plain_text_message)); @@ -1211,11 +1215,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa 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())); + Message message = new Message(conversation, body, conversation.getNextEncryption()); 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())); + activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption()); } } } -- cgit v1.2.3 From 26ac7c90306a566850139a3038a45402a964fcfd Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 31 Jul 2015 13:08:35 +0200 Subject: added missing carbon column message table create statement fixes #1310 --- src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java | 1 + 1 file changed, 1 insertion(+) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index b3fce845..8ac2884e 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -140,6 +140,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Message.RELATIVE_FILE_PATH + " TEXT, " + Message.SERVER_MSG_ID + " TEXT, " + Message.FINGERPRINT + " TEXT, " + + Message.CARBON + " INTEGER, " + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY(" + Message.CONVERSATION + ") REFERENCES " + Conversation.TABLENAME + "(" + Conversation.UUID -- cgit v1.2.3 From 5c421da1e1bd4c6008c36b17569c3dbc5d5e918b Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 31 Jul 2015 17:59:41 +0200 Subject: Change to new wire protocol version --- .../crypto/axolotl/AxolotlService.java | 27 ++-- .../crypto/axolotl/OnMessageCreatedCallback.java | 5 + .../crypto/axolotl/XmppAxolotlMessage.java | 143 ++++++++++++++------- .../crypto/axolotl/XmppAxolotlSession.java | 8 +- .../siacs/conversations/parser/MessageParser.java | 2 +- 5 files changed, 112 insertions(+), 73 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java (limited to 'src/main/java/eu') 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 d1bfe2d4..e4420898 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -583,23 +583,15 @@ public class AxolotlService { if(findSessionsforContact(message.getContact()).isEmpty()) { return null; } - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign headers..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign keyElements..."); for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) { Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); - //if(!session.isTrusted()) { - // TODO: handle this properly - // continue; - // } - axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey())); + axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey())); } - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own headers..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own keyElements..."); for (XmppAxolotlSession session : findOwnSessions()) { Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); - // if(!session.isTrusted()) { - // TODO: handle this properly - // continue; - // } - axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey())); + axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey())); } return axolotlMessage; @@ -651,7 +643,6 @@ public class AxolotlService { XmppAxolotlSession session = sessions.get(senderAddress); 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 IdentityKey identityKey = axolotlStore.loadSession(senderAddress).getSessionState().getRemoteIdentityKey(); if ( identityKey != null ) { session = new XmppAxolotlSession(account, axolotlStore, senderAddress, identityKey.getFingerprint().replaceAll("\\s", "")); @@ -661,12 +652,12 @@ public class AxolotlService { newSession = true; } - for (XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) { - if (header.getRecipientDeviceId() == getOwnDeviceId()) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl header matching own device ID, processing..."); - byte[] payloadKey = session.processReceiving(header); + for (XmppAxolotlMessage.XmppAxolotlKeyElement keyElement : message.getKeyElements()) { + if (keyElement.getRecipientDeviceId() == getOwnDeviceId()) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl keyElement matching own device ID, processing..."); + byte[] payloadKey = session.processReceiving(keyElement); if (payloadKey != null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl header. Decrypting message..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl keyElement. Decrypting message..."); try{ plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint()); } catch (CryptoFailedException e) { diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java new file mode 100644 index 00000000..3d40a408 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java @@ -0,0 +1,5 @@ +package eu.siacs.conversations.crypto.axolotl; + +public interface OnMessageCreatedCallback { + void run(XmppAxolotlMessage message); +} 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 182c8f12..4725ce8a 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.crypto.axolotl; import android.support.annotation.Nullable; import android.util.Base64; +import android.util.Log; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -20,32 +21,46 @@ import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import eu.siacs.conversations.Config; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.Jid; public class XmppAxolotlMessage { + public static final String TAGNAME = "encrypted"; + public static final String HEADER = "header"; + public static final String SOURCEID = "sid"; + public static final String IVTAG = "iv"; + public static final String PAYLOAD = "payload"; + + private static final String KEYTYPE = "AES"; + private static final String CIPHERMODE = "AES/GCM/NoPadding"; + private static final String PROVIDER = "BC"; + private byte[] innerKey; - private byte[] ciphertext; - private byte[] iv; - private final Set headers; + private byte[] ciphertext = null; + private byte[] iv = null; + private final Set keyElements; private final Jid from; private final int sourceDeviceId; - public static class XmppAxolotlMessageHeader { + public static class XmppAxolotlKeyElement { + public static final String TAGNAME = "key"; + public static final String REMOTEID = "rid"; + private final int recipientDeviceId; private final byte[] content; - public XmppAxolotlMessageHeader(int deviceId, byte[] content) { + public XmppAxolotlKeyElement(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); + public XmppAxolotlKeyElement(Element keyElement) { + if(TAGNAME.equals(keyElement.getName())) { + this.recipientDeviceId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); + this.content = Base64.decode(keyElement.getContent(),Base64.DEFAULT); } else { - throw new IllegalArgumentException("Argument not a

Element!"); + throw new IllegalArgumentException("Argument not a <"+TAGNAME+"> Element!"); } } @@ -58,11 +73,10 @@ public class XmppAxolotlMessage { } public Element toXml() { - Element headerElement = new Element("header"); - // TODO: generate XML - headerElement.setAttribute("rid", getRecipientDeviceId()); - headerElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT)); - return headerElement; + Element keyElement = new Element(TAGNAME); + keyElement.setAttribute(REMOTEID, getRecipientDeviceId()); + keyElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT)); + return keyElement; } } @@ -90,42 +104,69 @@ public class XmppAxolotlMessage { } } - public XmppAxolotlMessage(Jid from, Element axolotlMessage) { + public XmppAxolotlMessage(Jid from, Element axolotlMessage) throws IllegalArgumentException { this.from = from; - 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)); + Element header = axolotlMessage.findChild(HEADER); + this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID)); + this.keyElements = new HashSet<>(); + for(Element keyElement:header.getChildren()) { + switch(keyElement.getName()) { + case XmppAxolotlKeyElement.TAGNAME: + keyElements.add(new XmppAxolotlKeyElement(keyElement)); break; - case "message": - iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT); - ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT); + case IVTAG: + if ( this.iv != null) { + throw new IllegalArgumentException("Duplicate iv entry"); + } + iv = Base64.decode(keyElement.getContent(),Base64.DEFAULT); break; default: + Log.w(Config.LOGTAG, "Unexpected element in header: "+ keyElement.toString()); break; } } + Element payloadElement = axolotlMessage.findChild(PAYLOAD); + if ( payloadElement != null ) { + ciphertext = Base64.decode(payloadElement.getContent(), Base64.DEFAULT); + } } - public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException{ + public XmppAxolotlMessage(Jid from, int sourceDeviceId) { this.from = from; this.sourceDeviceId = sourceDeviceId; - this.headers = new HashSet<>(); + this.keyElements = new HashSet<>(); + this.iv = generateIv(); + this.innerKey = generateKey(); + } + + public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException{ + this(from, sourceDeviceId); this.encrypt(plaintext); } - private void encrypt(String plaintext) throws CryptoFailedException { + private static byte[] generateKey() { try { - KeyGenerator generator = KeyGenerator.getInstance("AES"); + KeyGenerator generator = KeyGenerator.getInstance(KEYTYPE); generator.init(128); - SecretKey secretKey = generator.generateKey(); - SecureRandom random = new SecureRandom(); - this.iv = new byte[16]; - random.nextBytes(iv); + return generator.generateKey().getEncoded(); + } catch (NoSuchAlgorithmException e) { + Log.e(Config.LOGTAG, e.getMessage()); + return null; + } + } + + private static byte[] generateIv() { + SecureRandom random = new SecureRandom(); + byte[] iv = new byte[16]; + random.nextBytes(iv); + return iv; + } + + private void encrypt(String plaintext) throws CryptoFailedException { + try { + SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC"); + Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); this.innerKey = secretKey.getEncoded(); this.ciphertext = cipher.doFinal(plaintext.getBytes()); @@ -148,13 +189,13 @@ public class XmppAxolotlMessage { return ciphertext; } - public Set getHeaders() { - return headers; + public Set getKeyElements() { + return keyElements; } - public void addHeader(@Nullable XmppAxolotlMessageHeader header) { - if (header != null) { - headers.add(header); + public void addKeyElement(@Nullable XmppAxolotlKeyElement keyElement) { + if (keyElement != null) { + keyElements.add(keyElement); } } @@ -167,16 +208,18 @@ public class XmppAxolotlMessage { } 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 encryptionElement= new Element(TAGNAME, AxolotlService.PEP_PREFIX); + Element headerElement = encryptionElement.addChild(HEADER); + headerElement.setAttribute(SOURCEID, sourceDeviceId); + for(XmppAxolotlKeyElement header: keyElements) { + headerElement.addChild(header.toXml()); + } + headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT)); + if ( ciphertext != null ) { + Element payload = encryptionElement.addChild(PAYLOAD); + payload.setContent(Base64.encodeToString(ciphertext, Base64.DEFAULT)); } - Element payload = message.addChild("message"); - payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT)); - payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT)); - return message; + return encryptionElement; } @@ -184,8 +227,8 @@ public class XmppAxolotlMessage { XmppAxolotlPlaintextMessage plaintextMessage = null; try { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC"); - SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); + SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index d58b2dd8..dc4c6777 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -69,7 +69,7 @@ public class XmppAxolotlSession { } @Nullable - public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) { + public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlKeyElement incomingHeader) { byte[] plaintext = null; SQLiteAxolotlStore.Trust trust = getTrust(); switch (trust) { @@ -117,12 +117,12 @@ public class XmppAxolotlSession { } @Nullable - public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(@NonNull byte[] outgoingMessage) { + public XmppAxolotlMessage.XmppAxolotlKeyElement processSending(@NonNull byte[] outgoingMessage) { SQLiteAxolotlStore.Trust trust = getTrust(); if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); - XmppAxolotlMessage.XmppAxolotlMessageHeader header = - new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), + XmppAxolotlMessage.XmppAxolotlKeyElement header = + new XmppAxolotlMessage.XmppAxolotlKeyElement(remoteAddress.getDeviceId(), ciphertextMessage.serialize()); return header; } else { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 4bee34a9..47c320f3 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -272,7 +272,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(XmppAxolotlMessage.TAGNAME, AxolotlService.PEP_PREFIX); int status; final Jid counterpart; final Jid to = packet.getTo(); -- cgit v1.2.3 From 50b14434eeda183d1d197a378239654a8db8b3a8 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 31 Jul 2015 18:05:32 +0200 Subject: Reformat code --- .../crypto/axolotl/AxolotlService.java | 143 +++++++++++---------- .../crypto/axolotl/XmppAxolotlMessage.java | 28 ++-- .../crypto/axolotl/XmppAxolotlSession.java | 2 +- 3 files changed, 87 insertions(+), 86 deletions(-) (limited to 'src/main/java/eu') 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 e4420898..4cc61b37 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -126,7 +126,7 @@ public class AxolotlService { private void putDevicesForJid(String bareJid, List deviceIds, SQLiteAxolotlStore store) { for (Integer deviceId : deviceIds) { AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building session for remote address: "+axolotlAddress.toString()); + 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)); } @@ -163,9 +163,9 @@ public class AxolotlService { private static class FetchStatusMap extends AxolotlAddressMap { } - + public static String getLogprefix(Account account) { - return LOGPREFIX+" ("+account.getJid().toBareJid().toString()+"): "; + return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): "; } public AxolotlService(Account account, XmppConnectionService connectionService) { @@ -238,7 +238,7 @@ public class AxolotlService { private void setTrustOnSessions(final Jid jid, @NonNull final Set deviceIds, final SQLiteAxolotlStore.Trust from, final SQLiteAxolotlStore.Trust to) { - for(Integer deviceId:deviceIds) { + for (Integer deviceId : deviceIds) { AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId); XmppAxolotlSession session = sessions.get(address); if (session != null && session.getFingerprint() != null @@ -249,13 +249,13 @@ public class AxolotlService { } public void registerDevices(final Jid jid, @NonNull final Set deviceIds) { - if(jid.toBareJid().equals(account.getJid().toBareJid())) { + if (jid.toBareJid().equals(account.getJid().toBareJid())) { if (deviceIds.contains(getOwnDeviceId())) { deviceIds.remove(getOwnDeviceId()); } - for(Integer deviceId : deviceIds) { - AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(),deviceId); - if(sessions.get(ownDeviceAddress) == null) { + for (Integer deviceId : deviceIds) { + AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId); + if (sessions.get(ownDeviceAddress) == null) { buildSessionFromPEP(null, ownDeviceAddress, false); } } @@ -276,7 +276,7 @@ public class AxolotlService { Set deviceIds = new HashSet<>(); deviceIds.add(getOwnDeviceId()); IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"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) { @@ -286,7 +286,7 @@ public class AxolotlService { } public void purgeKey(IdentityKey identityKey) { - axolotlStore.setFingerprintTrust(identityKey.getFingerprint().replaceAll("\\s",""), SQLiteAxolotlStore.Trust.COMPROMISED); + axolotlStore.setFingerprintTrust(identityKey.getFingerprint().replaceAll("\\s", ""), SQLiteAxolotlStore.Trust.COMPROMISED); } public void publishOwnDeviceIdIfNeeded() { @@ -302,7 +302,7 @@ public class AxolotlService { if (!deviceIds.contains(getOwnDeviceId())) { deviceIds.add(getOwnDeviceId()); IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"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) { @@ -323,19 +323,19 @@ public class AxolotlService { Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); boolean flush = false; if (bundle == null) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received invalid bundle:" + packet); - bundle = new PreKeyBundle(-1, -1, -1 , null, -1, null, null, null); + 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.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"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.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); changed = true; } @@ -344,16 +344,16 @@ public class AxolotlService { int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size(); try { signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId()); - if ( flush - ||!bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) + if (flush + || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"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.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"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; @@ -375,32 +375,32 @@ public class AxolotlService { int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size(); if (newKeys > 0) { List newRecords = KeyHelper.generatePreKeys( - axolotlStore.getCurrentPreKeyId()+1, newKeys); + axolotlStore.getCurrentPreKeyId() + 1, newKeys); preKeyRecords.addAll(newRecords); for (PreKeyRecord record : newRecords) { axolotlStore.storePreKey(record.getId(), record); } changed = true; - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding " + newKeys + " new preKeys to PEP."); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP."); } - if(changed) { + if (changed) { IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), preKeyRecords, getOwnDeviceId()); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+ ": 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, AxolotlService.getLogprefix(account)+"Published bundle, got: " + packet); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Published bundle, got: " + packet); } }); } } catch (InvalidKeyException e) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); - return; + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); + return; } } }); @@ -411,8 +411,9 @@ public class AxolotlService { Jid jid = contact.getJid().toBareJid(); AxolotlAddress address = new AxolotlAddress(jid.toString(), 0); return sessions.hasAny(address) || - ( deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty()); + (deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty()); } + public SQLiteAxolotlStore.Trust getFingerprintTrust(String fingerprint) { return axolotlStore.getFingerprintTrust(fingerprint); } @@ -427,10 +428,10 @@ public class AxolotlService { try { IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice( Jid.fromString(address.getName()), address.getDeviceId()); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Retrieving bundle: " + bundlesPacket); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket); mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() { private void finish() { - AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(),0); + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0); if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) { if (flushWaitingQueueAfterFetch && conversation != null) { @@ -438,7 +439,7 @@ public class AxolotlService { new Conversation.OnMessageFound() { @Override public void onMessageFound(Message message) { - processSending(message,false); + processSending(message, false); } }); } @@ -448,12 +449,12 @@ public class AxolotlService { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"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.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"preKey IQ packet invalid: " + packet); + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet); fetchStatusMap.put(address, FetchStatus.ERROR); finish(); return; @@ -480,8 +481,8 @@ public class AxolotlService { 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) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error building session for " + address + ": " + } catch (UntrustedIdentityException | InvalidKeyException e) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": " + e.getClass().getName() + ", " + e.getMessage()); fetchStatusMap.put(address, FetchStatus.ERROR); } @@ -490,7 +491,7 @@ public class AxolotlService { } }); } catch (InvalidJidException e) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got address with invalid jid: " + address.getName()); + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName()); } } @@ -498,13 +499,13 @@ public class AxolotlService { 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)) { + if (deviceIds.get(contactJid) != null) { + for (Integer foreignId : this.deviceIds.get(contactJid)) { AxolotlAddress address = new AxolotlAddress(contactJid.toString(), foreignId); - if(sessions.get(address) == null) { + 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..."); + 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 { @@ -516,13 +517,13 @@ public class AxolotlService { } else { Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!"); } - if(deviceIds.get(account.getJid().toBareJid()) != null) { - for(Integer ownId:this.deviceIds.get(account.getJid().toBareJid())) { + if (deviceIds.get(account.getJid().toBareJid()) != null) { + for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) { AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId); - if(sessions.get(address) == null) { + 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..."); + 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 { @@ -543,12 +544,12 @@ public class AxolotlService { for (AxolotlAddress address : addresses) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString()); FetchStatus status = fetchStatusMap.get(address); - if ( status == null || status == FetchStatus.ERROR ) { - fetchStatusMap.put(address, FetchStatus.PENDING); - this.buildSessionFromPEP(conversation, address, flushWaitingQueueAfterFetch); - newSessions = true; + if (status == null || status == FetchStatus.ERROR) { + fetchStatusMap.put(address, FetchStatus.PENDING); + this.buildSessionFromPEP(conversation, address, flushWaitingQueueAfterFetch); + newSessions = true; } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already fetching bundle for " + address.toString()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString()); } } @@ -556,18 +557,18 @@ public class AxolotlService { } public boolean hasPendingKeyFetches(Conversation conversation) { - AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(),0); - AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(),0); + 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); + || fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING); } @Nullable - public XmppAxolotlMessage encrypt(Message message ){ + public XmppAxolotlMessage encrypt(Message message) { final String content; if (message.hasFileOnRemoteHost()) { - content = message.getFileParams().url.toString(); + content = message.getFileParams().url.toString(); } else { content = message.getBody(); } @@ -580,17 +581,17 @@ public class AxolotlService { return null; } - if(findSessionsforContact(message.getContact()).isEmpty()) { + if (findSessionsforContact(message.getContact()).isEmpty()) { return null; } - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign keyElements..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements..."); for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) { - Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey())); } - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own keyElements..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements..."); for (XmppAxolotlSession session : findOwnSessions()) { - Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey())); } @@ -606,19 +607,19 @@ public class AxolotlService { mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); //mXmppConnectionService.updateConversationUi(); } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Generated message, caching: " + message.getUuid()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid()); messageCache.put(message.getUuid(), axolotlMessage); - mXmppConnectionService.resendMessage(message,delay); + mXmppConnectionService.resendMessage(message, delay); } } }); } - public void prepareMessage(final Message message,final boolean delay) { + public void prepareMessage(final Message message, final boolean delay) { if (!messageCache.containsKey(message.getUuid())) { boolean newSessions = createSessionsIfNeeded(message.getConversation(), true); if (!newSessions) { - this.processSending(message,delay); + this.processSending(message, delay); } } } @@ -626,10 +627,10 @@ public class AxolotlService { public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) { XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid()); if (axolotlMessage != null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Cache hit: " + message.getUuid()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid()); messageCache.remove(message.getUuid()); } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Cache miss: " + message.getUuid()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid()); } return axolotlMessage; } @@ -642,9 +643,9 @@ public class AxolotlService { boolean newSession = false; XmppAxolotlSession session = sessions.get(senderAddress); if (session == null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"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); IdentityKey identityKey = axolotlStore.loadSession(senderAddress).getSessionState().getRemoteIdentityKey(); - if ( identityKey != null ) { + if (identityKey != null) { session = new XmppAxolotlSession(account, axolotlStore, senderAddress, identityKey.getFingerprint().replaceAll("\\s", "")); } else { session = new XmppAxolotlSession(account, axolotlStore, senderAddress); @@ -654,11 +655,11 @@ public class AxolotlService { for (XmppAxolotlMessage.XmppAxolotlKeyElement keyElement : message.getKeyElements()) { if (keyElement.getRecipientDeviceId() == getOwnDeviceId()) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl keyElement matching own device ID, processing..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found axolotl keyElement matching own device ID, processing..."); byte[] payloadKey = session.processReceiving(keyElement); if (payloadKey != null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl keyElement. Decrypting message..."); - try{ + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got payload key from axolotl keyElement. Decrypting message..."); + try { plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint()); } catch (CryptoFailedException e) { Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage()); 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 4725ce8a..4e954a72 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -56,11 +56,11 @@ public class XmppAxolotlMessage { } public XmppAxolotlKeyElement(Element keyElement) { - if(TAGNAME.equals(keyElement.getName())) { + if (TAGNAME.equals(keyElement.getName())) { this.recipientDeviceId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); - this.content = Base64.decode(keyElement.getContent(),Base64.DEFAULT); + this.content = Base64.decode(keyElement.getContent(), Base64.DEFAULT); } else { - throw new IllegalArgumentException("Argument not a <"+TAGNAME+"> Element!"); + throw new IllegalArgumentException("Argument not a <" + TAGNAME + "> Element!"); } } @@ -109,24 +109,24 @@ public class XmppAxolotlMessage { Element header = axolotlMessage.findChild(HEADER); this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID)); this.keyElements = new HashSet<>(); - for(Element keyElement:header.getChildren()) { - switch(keyElement.getName()) { + for (Element keyElement : header.getChildren()) { + switch (keyElement.getName()) { case XmppAxolotlKeyElement.TAGNAME: keyElements.add(new XmppAxolotlKeyElement(keyElement)); break; case IVTAG: - if ( this.iv != null) { + if (this.iv != null) { throw new IllegalArgumentException("Duplicate iv entry"); } - iv = Base64.decode(keyElement.getContent(),Base64.DEFAULT); + iv = Base64.decode(keyElement.getContent(), Base64.DEFAULT); break; default: - Log.w(Config.LOGTAG, "Unexpected element in header: "+ keyElement.toString()); + Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement.toString()); break; } } Element payloadElement = axolotlMessage.findChild(PAYLOAD); - if ( payloadElement != null ) { + if (payloadElement != null) { ciphertext = Base64.decode(payloadElement.getContent(), Base64.DEFAULT); } } @@ -139,7 +139,7 @@ public class XmppAxolotlMessage { this.innerKey = generateKey(); } - public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException{ + public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException { this(from, sourceDeviceId); this.encrypt(plaintext); } @@ -199,7 +199,7 @@ public class XmppAxolotlMessage { } } - public byte[] getInnerKey(){ + public byte[] getInnerKey() { return innerKey; } @@ -208,14 +208,14 @@ public class XmppAxolotlMessage { } public Element toXml() { - Element encryptionElement= new Element(TAGNAME, AxolotlService.PEP_PREFIX); + Element encryptionElement = new Element(TAGNAME, AxolotlService.PEP_PREFIX); Element headerElement = encryptionElement.addChild(HEADER); headerElement.setAttribute(SOURCEID, sourceDeviceId); - for(XmppAxolotlKeyElement header: keyElements) { + for (XmppAxolotlKeyElement header : keyElements) { headerElement.addChild(header.toXml()); } headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT)); - if ( ciphertext != null ) { + if (ciphertext != null) { Element payload = encryptionElement.addChild(PAYLOAD); payload.setContent(Base64.encodeToString(ciphertext, Base64.DEFAULT)); } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index dc4c6777..d60e7715 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -65,7 +65,7 @@ public class XmppAxolotlSession { protected SQLiteAxolotlStore.Trust getTrust() { SQLiteAxolotlStore.Trust trust = sqLiteAxolotlStore.getFingerprintTrust(fingerprint); - return (trust == null)? SQLiteAxolotlStore.Trust.UNDECIDED : trust; + return (trust == null) ? SQLiteAxolotlStore.Trust.UNDECIDED : trust; } @Nullable -- cgit v1.2.3 From 909f761ca1659938cf5f9d7206ee24d54faa8550 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 31 Jul 2015 21:12:34 +0200 Subject: Refactor axolotl message processing workflow XmppAxolotlMessage is now entirely responsible for handling encryption and decryption of messages, only leveraging XmppAxolotlSession as a packing/unpacking primitive for payload keys. Removed pseudo-dead session generation code step from prepareMessage function, as sessions have been created by invoking the TrustKeysActivity for a while now. Added prepareKeyTransportMessage function, which creates a message with no payload. The key that is packed into the header keyElements can then be used for other purposes (e.g. encrypted file transfer). --- .../crypto/axolotl/AxolotlService.java | 113 ++++++++-------- .../crypto/axolotl/XmppAxolotlMessage.java | 142 +++++++++------------ .../crypto/axolotl/XmppAxolotlSession.java | 13 +- .../conversations/generator/MessageGenerator.java | 2 +- .../siacs/conversations/parser/MessageParser.java | 4 +- .../services/XmppConnectionService.java | 2 +- .../conversations/ui/ConversationActivity.java | 2 +- 7 files changed, 121 insertions(+), 157 deletions(-) (limited to 'src/main/java/eu') 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 4cc61b37..4cb1c90c 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -256,7 +256,7 @@ public class AxolotlService { for (Integer deviceId : deviceIds) { AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId); if (sessions.get(ownDeviceAddress) == null) { - buildSessionFromPEP(null, ownDeviceAddress, false); + buildSessionFromPEP(ownDeviceAddress); } } } @@ -422,7 +422,7 @@ public class AxolotlService { axolotlStore.setFingerprintTrust(fingerprint, trust); } - private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address, final boolean flushWaitingQueueAfterFetch) { + private void buildSessionFromPEP(final AxolotlAddress address) { Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.getDeviceId()); try { @@ -434,15 +434,6 @@ public class AxolotlService { AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0); if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) { - if (flushWaitingQueueAfterFetch && conversation != null) { - conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_AXOLOTL, - new Conversation.OnMessageFound() { - @Override - public void onMessageFound(Message message) { - processSending(message, false); - } - }); - } mXmppConnectionService.keyStatusUpdated(); } } @@ -537,7 +528,7 @@ public class AxolotlService { return addresses; } - public boolean createSessionsIfNeeded(final Conversation conversation, final boolean flushWaitingQueueAfterFetch) { + public boolean createSessionsIfNeeded(final Conversation conversation) { Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed..."); boolean newSessions = false; Set addresses = findDevicesWithoutSession(conversation); @@ -546,7 +537,9 @@ public class AxolotlService { FetchStatus status = fetchStatusMap.get(address); if (status == null || status == FetchStatus.ERROR) { fetchStatusMap.put(address, FetchStatus.PENDING); - this.buildSessionFromPEP(conversation, address, flushWaitingQueueAfterFetch); + this.buildSessionFromPEP(address); + newSessions = true; + } else if (status == FetchStatus.PENDING) { newSessions = true; } else { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString()); @@ -565,40 +558,52 @@ 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; - try { - axolotlMessage = new XmppAxolotlMessage(message.getContact().getJid().toBareJid(), - getOwnDeviceId(), content); - } catch (CryptoFailedException e) { - Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage()); - return null; - } + private XmppAxolotlMessage buildHeader(Contact contact) { + final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage( + contact.getJid().toBareJid(), getOwnDeviceId()); - if (findSessionsforContact(message.getContact()).isEmpty()) { + Set contactSessions = findSessionsforContact(contact); + Set ownSessions = findOwnSessions(); + if (contactSessions.isEmpty()) { return null; } Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements..."); - for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) { + for (XmppAxolotlSession session : contactSessions) { Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); - axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey())); + axolotlMessage.addDevice(session); } Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements..."); - for (XmppAxolotlSession session : findOwnSessions()) { + for (XmppAxolotlSession session : ownSessions) { Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); - axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey())); + axolotlMessage.addDevice(session); + } + + return axolotlMessage; + } + + @Nullable + public XmppAxolotlMessage encrypt(Message message) { + XmppAxolotlMessage axolotlMessage = buildHeader(message.getContact()); + + if (axolotlMessage != null) { + final String content; + if (message.hasFileOnRemoteHost()) { + content = message.getFileParams().url.toString(); + } else { + content = message.getBody(); + } + try { + axolotlMessage.encrypt(content); + } catch (CryptoFailedException e) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage()); + return null; + } } return axolotlMessage; } - private void processSending(final Message message, final boolean delay) { + public void preparePayloadMessage(final Message message, final boolean delay) { executor.execute(new Runnable() { @Override public void run() { @@ -615,13 +620,14 @@ public class AxolotlService { }); } - public void prepareMessage(final Message message, final boolean delay) { - if (!messageCache.containsKey(message.getUuid())) { - boolean newSessions = createSessionsIfNeeded(message.getConversation(), true); - if (!newSessions) { - this.processSending(message, delay); + public void prepareKeyTransportMessage(final Contact contact, final OnMessageCreatedCallback onMessageCreatedCallback) { + executor.execute(new Runnable() { + @Override + public void run() { + XmppAxolotlMessage axolotlMessage = buildHeader(contact); + onMessageCreatedCallback.run(axolotlMessage); } - } + }); } public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) { @@ -653,26 +659,15 @@ public class AxolotlService { newSession = true; } - for (XmppAxolotlMessage.XmppAxolotlKeyElement keyElement : message.getKeyElements()) { - if (keyElement.getRecipientDeviceId() == getOwnDeviceId()) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found axolotl keyElement matching own device ID, processing..."); - byte[] payloadKey = session.processReceiving(keyElement); - if (payloadKey != null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got payload key from axolotl keyElement. Decrypting message..."); - try { - plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint()); - } catch (CryptoFailedException e) { - Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage()); - break; - } - } - Integer preKeyId = session.getPreKeyId(); - if (preKeyId != null) { - publishBundlesIfNeeded(); - session.resetPreKeyId(); - } - break; + try { + plaintextMessage = message.decrypt(session, getOwnDeviceId()); + Integer preKeyId = session.getPreKeyId(); + if (preKeyId != null) { + publishBundlesIfNeeded(); + session.resetPreKeyId(); } + } catch (CryptoFailedException e) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage()); } if (newSession && plaintextMessage != 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 4e954a72..fa6d895a 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.crypto.axolotl; -import android.support.annotation.Nullable; import android.util.Base64; import android.util.Log; @@ -9,8 +8,9 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SecureRandom; -import java.util.HashSet; -import java.util.Set; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -26,9 +26,11 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.Jid; public class XmppAxolotlMessage { - public static final String TAGNAME = "encrypted"; + public static final String CONTAINERTAG = "encrypted"; public static final String HEADER = "header"; public static final String SOURCEID = "sid"; + public static final String KEYTAG = "key"; + public static final String REMOTEID = "rid"; public static final String IVTAG = "iv"; public static final String PAYLOAD = "payload"; @@ -39,54 +41,15 @@ public class XmppAxolotlMessage { private byte[] innerKey; private byte[] ciphertext = null; private byte[] iv = null; - private final Set keyElements; + private final Map keys; private final Jid from; private final int sourceDeviceId; - public static class XmppAxolotlKeyElement { - public static final String TAGNAME = "key"; - public static final String REMOTEID = "rid"; - - private final int recipientDeviceId; - private final byte[] content; - - public XmppAxolotlKeyElement(int deviceId, byte[] content) { - this.recipientDeviceId = deviceId; - this.content = content; - } - - public XmppAxolotlKeyElement(Element keyElement) { - if (TAGNAME.equals(keyElement.getName())) { - this.recipientDeviceId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); - this.content = Base64.decode(keyElement.getContent(), Base64.DEFAULT); - } else { - throw new IllegalArgumentException("Argument not a <" + TAGNAME + "> Element!"); - } - } - - public int getRecipientDeviceId() { - return recipientDeviceId; - } - - public byte[] getContents() { - return content; - } - - public Element toXml() { - Element keyElement = new Element(TAGNAME); - keyElement.setAttribute(REMOTEID, getRecipientDeviceId()); - keyElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT)); - return keyElement; - } - } - public static class XmppAxolotlPlaintextMessage { - private final XmppAxolotlSession session; private final String plaintext; private final String fingerprint; - public XmppAxolotlPlaintextMessage(XmppAxolotlSession session, String plaintext, String fingerprint) { - this.session = session; + public XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) { this.plaintext = plaintext; this.fingerprint = fingerprint; } @@ -95,24 +58,28 @@ public class XmppAxolotlMessage { return plaintext; } - public XmppAxolotlSession getSession() { - return session; - } public String getFingerprint() { return fingerprint; } } - public XmppAxolotlMessage(Jid from, Element axolotlMessage) throws IllegalArgumentException { + private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException { this.from = from; Element header = axolotlMessage.findChild(HEADER); this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID)); - this.keyElements = new HashSet<>(); - for (Element keyElement : header.getChildren()) { + List keyElements = header.getChildren(); + this.keys = new HashMap<>(keyElements.size()); + for (Element keyElement : keyElements) { switch (keyElement.getName()) { - case XmppAxolotlKeyElement.TAGNAME: - keyElements.add(new XmppAxolotlKeyElement(keyElement)); + case KEYTAG: + try { + Integer recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); + byte[] key = Base64.decode(keyElement.getContent(), Base64.DEFAULT); + this.keys.put(recipientId, key); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(e); + } break; case IVTAG: if (this.iv != null) { @@ -134,14 +101,13 @@ public class XmppAxolotlMessage { public XmppAxolotlMessage(Jid from, int sourceDeviceId) { this.from = from; this.sourceDeviceId = sourceDeviceId; - this.keyElements = new HashSet<>(); + this.keys = new HashMap<>(); this.iv = generateIv(); this.innerKey = generateKey(); } - public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException { - this(from, sourceDeviceId); - this.encrypt(plaintext); + public static XmppAxolotlMessage fromElement(Element element, Jid from) { + return new XmppAxolotlMessage(element, from); } private static byte[] generateKey() { @@ -162,7 +128,7 @@ public class XmppAxolotlMessage { return iv; } - private void encrypt(String plaintext) throws CryptoFailedException { + public void encrypt(String plaintext) throws CryptoFailedException { try { SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); @@ -189,13 +155,10 @@ public class XmppAxolotlMessage { return ciphertext; } - public Set getKeyElements() { - return keyElements; - } - - public void addKeyElement(@Nullable XmppAxolotlKeyElement keyElement) { - if (keyElement != null) { - keyElements.add(keyElement); + public void addDevice(XmppAxolotlSession session) { + byte[] key = session.processSending(innerKey); + if (key != null) { + keys.put(session.getRemoteAddress().getDeviceId(), key); } } @@ -207,12 +170,15 @@ public class XmppAxolotlMessage { return this.iv; } - public Element toXml() { - Element encryptionElement = new Element(TAGNAME, AxolotlService.PEP_PREFIX); + public Element toElement() { + Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX); Element headerElement = encryptionElement.addChild(HEADER); headerElement.setAttribute(SOURCEID, sourceDeviceId); - for (XmppAxolotlKeyElement header : keyElements) { - headerElement.addChild(header.toXml()); + for (Map.Entry keyEntry : keys.entrySet()) { + Element keyElement = new Element(KEYTAG); + keyElement.setAttribute(REMOTEID, keyEntry.getKey()); + keyElement.setContent(Base64.encodeToString(keyEntry.getValue(), Base64.DEFAULT)); + headerElement.addChild(keyElement); } headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT)); if (ciphertext != null) { @@ -222,24 +188,30 @@ public class XmppAxolotlMessage { return encryptionElement; } + public byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) { + byte[] encryptedKey = keys.get(sourceDeviceId); + return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null; + } - public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, byte[] key, String fingerprint) throws CryptoFailedException { + public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { XmppAxolotlPlaintextMessage plaintextMessage = null; - try { - - Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); - SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); - IvParameterSpec ivSpec = new IvParameterSpec(iv); - - cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); - - String plaintext = new String(cipher.doFinal(ciphertext)); - plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext, fingerprint); - - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | InvalidAlgorithmParameterException | IllegalBlockSizeException - | BadPaddingException | NoSuchProviderException e) { - throw new CryptoFailedException(e); + byte[] key = unpackKey(session, sourceDeviceId); + if (key != null) { + try { + Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); + SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + + String plaintext = new String(cipher.doFinal(ciphertext)); + plaintextMessage = new XmppAxolotlPlaintextMessage(plaintext, session.getFingerprint()); + + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | InvalidAlgorithmParameterException | IllegalBlockSizeException + | BadPaddingException | NoSuchProviderException e) { + throw new CryptoFailedException(e); + } } return plaintextMessage; } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index d60e7715..6ed73da6 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -69,7 +69,7 @@ public class XmppAxolotlSession { } @Nullable - public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlKeyElement incomingHeader) { + public byte[] processReceiving(byte[] encryptedKey) { byte[] plaintext = null; SQLiteAxolotlStore.Trust trust = getTrust(); switch (trust) { @@ -79,7 +79,7 @@ public class XmppAxolotlSession { case TRUSTED: try { try { - PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); + PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey); Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", ""); if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) { @@ -93,7 +93,7 @@ public class XmppAxolotlSession { } } catch (InvalidMessageException | InvalidVersionException e) { Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received"); - WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); + WhisperMessage message = new WhisperMessage(encryptedKey); plaintext = cipher.decrypt(message); } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); @@ -117,14 +117,11 @@ public class XmppAxolotlSession { } @Nullable - public XmppAxolotlMessage.XmppAxolotlKeyElement processSending(@NonNull byte[] outgoingMessage) { + public byte[] processSending(@NonNull byte[] outgoingMessage) { SQLiteAxolotlStore.Trust trust = getTrust(); if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); - XmppAxolotlMessage.XmppAxolotlKeyElement header = - new XmppAxolotlMessage.XmppAxolotlKeyElement(remoteAddress.getDeviceId(), - ciphertextMessage.serialize()); - return header; + return ciphertextMessage.serialize(); } else { return null; } diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 04591672..a06a0ddd 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -63,7 +63,7 @@ public class MessageGenerator extends AbstractGenerator { if (axolotlMessage == null) { return null; } - packet.setAxolotlMessage(axolotlMessage.toXml()); + packet.setAxolotlMessage(axolotlMessage.toElement()); 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 47c320f3..a53e3a50 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -99,7 +99,7 @@ public class MessageParser extends AbstractParser implements 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(from.toBareJid(), axolotlMessage); + XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid()); XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage); if(plaintextMessage != null) { finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status); @@ -272,7 +272,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(XmppAxolotlMessage.TAGNAME, AxolotlService.PEP_PREFIX); + final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX); int status; final Jid counterpart; final Jid to = packet.getTo(); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 13b81090..61453ab0 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -764,7 +764,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } else { XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message); if (axolotlMessage == null) { - account.getAxolotlService().prepareMessage(message,delay); + account.getAxolotlService().preparePayloadMessage(message, delay); message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", "")); } else { packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index daf22122..f34e1a55 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -1265,7 +1265,7 @@ public class ConversationActivity extends XmppActivity || !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty(); boolean hasNoTrustedKeys = axolotlService.getNumTrustedKeys(mSelectedConversation.getContact()) == 0; if( hasPendingKeys || hasNoTrustedKeys) { - axolotlService.createSessionsIfNeeded(mSelectedConversation, false); + axolotlService.createSessionsIfNeeded(mSelectedConversation); Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class); intent.putExtra("contact", mSelectedConversation.getContact().getJid().toBareJid().toString()); intent.putExtra("account", mSelectedConversation.getAccount().getJid().toBareJid().toString()); -- cgit v1.2.3 From 6059b964569fb406bbc86a0ccb19e76851fba2b6 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 31 Jul 2015 23:28:09 +0200 Subject: Provide process function for key transport message --- .../crypto/axolotl/AxolotlService.java | 48 +++++++++++++++++----- .../crypto/axolotl/XmppAxolotlMessage.java | 33 ++++++++++++++- .../crypto/axolotl/XmppAxolotlSession.java | 20 ++++++--- .../siacs/conversations/parser/MessageParser.java | 2 +- 4 files changed, 84 insertions(+), 19 deletions(-) (limited to 'src/main/java/eu') 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 4cb1c90c..7a92a1f7 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -150,8 +150,13 @@ public class AxolotlService { @Override public void put(AxolotlAddress address, XmppAxolotlSession value) { super.put(address, value); + value.setNotFresh(); xmppConnectionService.syncRosterToDisk(account); } + + public void put(XmppAxolotlSession session) { + this.put(session.getRemoteAddress(), session); + } } private static enum FetchStatus { @@ -641,24 +646,32 @@ public class AxolotlService { return axolotlMessage; } - public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) { - XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; + private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + return (identityKey != null) + ? new XmppAxolotlSession(account, axolotlStore, address, + identityKey.getFingerprint().replaceAll("\\s", "")) + : null; + } + + private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) { AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(), message.getSenderDeviceId()); - - boolean newSession = false; XmppAxolotlSession session = sessions.get(senderAddress); if (session == null) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message); - IdentityKey identityKey = axolotlStore.loadSession(senderAddress).getSessionState().getRemoteIdentityKey(); - if (identityKey != null) { - session = new XmppAxolotlSession(account, axolotlStore, senderAddress, identityKey.getFingerprint().replaceAll("\\s", "")); - } else { + session = recreateUncachedSession(senderAddress); + if (session == null) { session = new XmppAxolotlSession(account, axolotlStore, senderAddress); } - newSession = true; } + return session; + } + public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) { + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; + + XmppAxolotlSession session = getReceivingSession(message); try { plaintextMessage = message.decrypt(session, getOwnDeviceId()); Integer preKeyId = session.getPreKeyId(); @@ -670,10 +683,23 @@ public class AxolotlService { Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage()); } - if (newSession && plaintextMessage != null) { - sessions.put(senderAddress, session); + if (session.isFresh() && plaintextMessage != null) { + sessions.put(session); } return plaintextMessage; } + + public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) { + XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage = null; + + XmppAxolotlSession session = getReceivingSession(message); + keyTransportMessage = message.getParameters(session, getOwnDeviceId()); + + if (session.isFresh() && keyTransportMessage != null) { + sessions.put(session); + } + + return keyTransportMessage; + } } 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 fa6d895a..cf950d6d 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -64,6 +64,30 @@ public class XmppAxolotlMessage { } } + public static class XmppAxolotlKeyTransportMessage { + private final String fingerprint; + private final byte[] key; + private final byte[] iv; + + public XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) { + this.fingerprint = fingerprint; + this.key = key; + this.iv = iv; + } + + public String getFingerprint() { + return fingerprint; + } + + public byte[] getKey() { + return key; + } + + public byte[] getIv() { + return iv; + } + } + private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException { this.from = from; Element header = axolotlMessage.findChild(HEADER); @@ -188,11 +212,18 @@ public class XmppAxolotlMessage { return encryptionElement; } - public byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) { + private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) { byte[] encryptedKey = keys.get(sourceDeviceId); return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null; } + public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) { + byte[] key = unpackKey(session, sourceDeviceId); + return (key != null) + ? new XmppAxolotlKeyTransportMessage(session.getFingerprint(), key, getIV()) + : null; + } + public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { XmppAxolotlPlaintextMessage plaintextMessage = null; byte[] key = unpackKey(session, sourceDeviceId); diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index 6ed73da6..46004a1a 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -23,16 +23,12 @@ import eu.siacs.conversations.entities.Account; public class XmppAxolotlSession { private final SessionCipher cipher; - private Integer preKeyId = null; private final SQLiteAxolotlStore sqLiteAxolotlStore; - - public AxolotlAddress getRemoteAddress() { - return remoteAddress; - } - private final AxolotlAddress remoteAddress; private final Account account; private String fingerprint = null; + private Integer preKeyId = null; + private boolean fresh = true; public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) { this(account, store, remoteAddress); @@ -59,6 +55,18 @@ public class XmppAxolotlSession { return fingerprint; } + public AxolotlAddress getRemoteAddress() { + return remoteAddress; + } + + public boolean isFresh() { + return fresh; + } + + public void setNotFresh() { + this.fresh = false; + } + protected void setTrust(SQLiteAxolotlStore.Trust trust) { sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust); } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index a53e3a50..5786487f 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -100,7 +100,7 @@ public class MessageParser extends AbstractParser implements Message finishedMessage = null; AxolotlService service = conversation.getAccount().getAxolotlService(); XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid()); - XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage); + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage); if(plaintextMessage != null) { finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status); finishedMessage.setAxolotlFingerprint(plaintextMessage.getFingerprint()); -- cgit v1.2.3 From 60cd307f73d5f31f25ba84541fbe1cce4aae2bc2 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 1 Aug 2015 01:19:16 +0200 Subject: enable axolotl encryption for jingle supported file transfers --- .../conversations/entities/DownloadableFile.java | 34 ++++---- .../conversations/http/HttpDownloadConnection.java | 25 ++---- .../conversations/http/HttpUploadConnection.java | 24 ++---- .../services/AbstractConnectionManager.java | 97 ++++++++++++++++++++++ .../services/XmppConnectionService.java | 2 +- .../xmpp/jingle/JingleConnection.java | 97 +++++++++++++++++----- .../xmpp/jingle/JingleInbandTransport.java | 10 +-- .../xmpp/jingle/JingleSocks5Transport.java | 6 +- .../conversations/xmpp/jingle/JingleTransport.java | 61 ++------------ .../conversations/xmpp/jingle/stanzas/Content.java | 5 +- 10 files changed, 223 insertions(+), 138 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java index 452bf815..d35a4b01 100644 --- a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java +++ b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java @@ -24,15 +24,7 @@ public class DownloadableFile extends File { } public long getExpectedSize() { - if (this.aeskey != null) { - if (this.expectedSize == 0) { - return 0; - } else { - return (this.expectedSize / 16 + 1) * 16; - } - } else { - return this.expectedSize; - } + return this.expectedSize; } public String getMimeType() { @@ -58,25 +50,33 @@ public class DownloadableFile extends File { this.sha1sum = sum; } - public void setKey(byte[] key) { - if (key.length == 48) { + public void setKeyAndIv(byte[] keyIvCombo) { + if (keyIvCombo.length == 48) { byte[] secretKey = new byte[32]; byte[] iv = new byte[16]; - System.arraycopy(key, 0, iv, 0, 16); - System.arraycopy(key, 16, secretKey, 0, 32); + System.arraycopy(keyIvCombo, 0, iv, 0, 16); + System.arraycopy(keyIvCombo, 16, secretKey, 0, 32); this.aeskey = secretKey; this.iv = iv; - } else if (key.length >= 32) { + } else if (keyIvCombo.length >= 32) { byte[] secretKey = new byte[32]; - System.arraycopy(key, 0, secretKey, 0, 32); + System.arraycopy(keyIvCombo, 0, secretKey, 0, 32); this.aeskey = secretKey; - } else if (key.length >= 16) { + } else if (keyIvCombo.length >= 16) { byte[] secretKey = new byte[16]; - System.arraycopy(key, 0, secretKey, 0, 16); + System.arraycopy(keyIvCombo, 0, secretKey, 0, 16); this.aeskey = secretKey; } } + public void setKey(byte[] key) { + this.aeskey = key; + } + + public void setIv(byte[] iv) { + this.iv = iv; + } + public byte[] getKey() { return this.aeskey; } diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 51479836..79e4654b 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -4,15 +4,7 @@ import android.content.Intent; import android.net.Uri; import android.util.Log; -import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.io.CipherOutputStream; -import org.bouncycastle.crypto.modes.AEADBlockCipher; -import org.bouncycastle.crypto.modes.GCMBlockCipher; -import org.bouncycastle.crypto.params.AEADParameters; -import org.bouncycastle.crypto.params.KeyParameter; - import java.io.BufferedInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; @@ -28,6 +20,8 @@ import eu.siacs.conversations.R; 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.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; @@ -90,7 +84,7 @@ public class HttpDownloadConnection implements Transferable { this.file = mXmppConnectionService.getFileBackend().getFile(message, false); String reference = mUrl.getRef(); if (reference != null && reference.length() == 96) { - this.file.setKey(CryptoHelper.hexToBytes(reference)); + this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference)); } if ((this.message.getEncryption() == Message.ENCRYPTION_OTR @@ -194,6 +188,8 @@ public class HttpDownloadConnection implements Transferable { private boolean interactive = false; + private OutputStream os; + public FileDownloader(boolean interactive) { this.interactive = interactive; } @@ -206,8 +202,10 @@ public class HttpDownloadConnection implements Transferable { updateImageBounds(); finish(); } catch (SSLHandshakeException e) { + FileBackend.close(os); changeStatus(STATUS_OFFER); } catch (IOException e) { + FileBackend.close(os); mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host); cancel(); } @@ -222,14 +220,7 @@ public class HttpDownloadConnection implements Transferable { BufferedInputStream is = new BufferedInputStream(connection.getInputStream()); file.getParentFile().mkdirs(); file.createNewFile(); - OutputStream os; - if (file.getKey() != null) { - AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); - cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); - os = new CipherOutputStream(new FileOutputStream(file), cipher); - } else { - os = new FileOutputStream(file); - } + os = AbstractConnectionManager.createOutputStream(file,true); long transmitted = 0; long expected = file.getExpectedSize(); int count = -1; diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index 70dcc642..b21d0d41 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -4,15 +4,8 @@ import android.app.PendingIntent; import android.content.Intent; import android.net.Uri; import android.util.Log; +import android.util.Pair; -import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.io.CipherInputStream; -import org.bouncycastle.crypto.modes.AEADBlockCipher; -import org.bouncycastle.crypto.modes.GCMBlockCipher; -import org.bouncycastle.crypto.params.AEADParameters; -import org.bouncycastle.crypto.params.KeyParameter; - -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -28,6 +21,7 @@ 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.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.UiCallback; import eu.siacs.conversations.utils.CryptoHelper; @@ -105,7 +99,7 @@ public class HttpUploadConnection implements Transferable { || message.getEncryption() == Message.ENCRYPTION_OTR) { this.key = new byte[48]; mXmppConnectionService.getRNG().nextBytes(this.key); - this.file.setKey(this.key); + this.file.setKeyAndIv(this.key); } Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD); @@ -152,15 +146,9 @@ public class HttpUploadConnection implements Transferable { if (connection instanceof HttpsURLConnection) { mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); } - if (file.getKey() != null) { - AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); - cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); - expected = cipher.getOutputSize((int) file.getSize()); - is = new CipherInputStream(new FileInputStream(file), cipher); - } else { - expected = (int) file.getSize(); - is = new FileInputStream(file); - } + Pair pair = AbstractConnectionManager.createInputStream(file,true); + is = pair.first; + expected = pair.second; connection.setRequestMethod("PUT"); connection.setFixedLengthStreamingMode(expected); connection.setDoOutput(true); diff --git a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java index 676a09c9..b7e7c8d3 100644 --- a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java @@ -1,5 +1,33 @@ package eu.siacs.conversations.services; +import android.util.Log; +import android.util.Pair; + +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.DownloadableFile; + public class AbstractConnectionManager { protected XmppConnectionService mXmppConnectionService; @@ -20,4 +48,73 @@ public class AbstractConnectionManager { return 524288; } } + + public static Pair createInputStream(DownloadableFile file, boolean gcm) { + FileInputStream is; + int size; + try { + is = new FileInputStream(file); + size = (int) file.getSize(); + if (file.getKey() == null) { + return new Pair(is,size); + } + } catch (FileNotFoundException e) { + return null; + } + try { + if (gcm) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + InputStream cis = new org.bouncycastle.crypto.io.CipherInputStream(is, cipher); + return new Pair<>(cis, cipher.getOutputSize(size)); + } else { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted input stream"); + return new Pair(new CipherInputStream(is, cipher),(size / 16 + 1) * 16); + } + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } + + public static OutputStream createOutputStream(DownloadableFile file, boolean gcm) { + FileOutputStream os; + try { + os = new FileOutputStream(file); + if (file.getKey() == null) { + return os; + } + } catch (FileNotFoundException e) { + return null; + } + try { + if (gcm) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + return new org.bouncycastle.crypto.io.CipherOutputStream(os, cipher); + } else { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted output stream"); + return new CipherOutputStream(os, cipher); + } + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 61453ab0..ffe587d6 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -755,6 +755,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } break; case Message.ENCRYPTION_AXOLOTL: + message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", "")); if (message.needsUploading()) { if (account.httpUploadAvailable() || message.fixCounterpart()) { this.sendFileMessage(message,delay); @@ -765,7 +766,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message); if (axolotlMessage == null) { account.getAxolotlService().preparePayloadMessage(message, delay); - message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", "")); } else { packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage); } 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 e15fc522..e9ca66b7 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -2,9 +2,11 @@ package eu.siacs.conversations.xmpp.jingle; import android.content.Intent; import android.net.Uri; -import android.os.SystemClock; import android.util.Log; +import android.util.Pair; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -14,13 +16,19 @@ import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.OnMessageCreatedCallback; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.TransferablePlaceholder; +import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jid.Jid; @@ -66,8 +74,13 @@ public class JingleConnection implements Transferable { private boolean acceptedAutomatically = false; + private XmppAxolotlMessage mXmppAxolotlMessage; + private JingleTransport transport = null; + private OutputStream mFileOutputStream; + private InputStream mFileInputStream; + private OnIqPacketReceived responseListener = new OnIqPacketReceived() { @Override @@ -113,6 +126,14 @@ public class JingleConnection implements Transferable { } }; + public InputStream getFileInputStream() { + return this.mFileInputStream; + } + + public OutputStream getFileOutputStream() { + return this.mFileOutputStream; + } + private OnProxyActivated onProxyActivated = new OnProxyActivated() { @Override @@ -194,7 +215,22 @@ public class JingleConnection implements Transferable { mXmppConnectionService.sendIqPacket(account,response,null); } - public void init(Message message) { + public void init(final Message message) { + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + Conversation conversation = message.getConversation(); + conversation.getAccount().getAxolotlService().prepareKeyTransportMessage(conversation.getContact(), new OnMessageCreatedCallback() { + @Override + public void run(XmppAxolotlMessage xmppAxolotlMessage) { + init(message, xmppAxolotlMessage); + } + }); + } else { + init(message, null); + } + } + + private void init(Message message, XmppAxolotlMessage xmppAxolotlMessage) { + this.mXmppAxolotlMessage = xmppAxolotlMessage; this.contentCreator = "initiator"; this.contentName = this.mJingleConnectionManager.nextRandomId(); this.message = message; @@ -238,8 +274,7 @@ public class JingleConnection implements Transferable { }); mergeCandidate(candidate); } else { - Log.d(Config.LOGTAG, - "no primary candidate of our own was found"); + Log.d(Config.LOGTAG,"no primary candidate of our own was found"); sendInitRequest(); } } @@ -267,13 +302,16 @@ public class JingleConnection implements Transferable { this.contentCreator = content.getAttribute("creator"); this.contentName = content.getAttribute("name"); this.transportId = content.getTransportId(); - this.mergeCandidates(JingleCandidate.parse(content.socks5transport() - .getChildren())); + this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren())); this.fileOffer = packet.getJingleContent().getFileOffer(); mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null); if (fileOffer != null) { + Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX); + if (encrypted != null) { + this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().toBareJid()); + } Element fileSize = fileOffer.findChild("size"); Element fileNameElement = fileOffer.findChild("name"); if (fileNameElement != null) { @@ -319,10 +357,8 @@ public class JingleConnection implements Transferable { message.setBody(Long.toString(size)); conversation.add(message); mXmppConnectionService.updateConversationUi(); - if (size < this.mJingleConnectionManager - .getAutoAcceptFileSize()) { - Log.d(Config.LOGTAG, "auto accepting file from " - + packet.getFrom()); + if (size < this.mJingleConnectionManager.getAutoAcceptFileSize()) { + Log.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom()); this.acceptedAutomatically = true; this.sendAccept(); } else { @@ -333,22 +369,32 @@ public class JingleConnection implements Transferable { + " allowed size:" + this.mJingleConnectionManager .getAutoAcceptFileSize()); - this.mXmppConnectionService.getNotificationService() - .push(message); + this.mXmppConnectionService.getNotificationService().push(message); } - this.file = this.mXmppConnectionService.getFileBackend() - .getFile(message, false); - if (message.getEncryption() == Message.ENCRYPTION_OTR) { + this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false); + if (mXmppAxolotlMessage != null) { + XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage); + if (transportMessage != null) { + message.setEncryption(Message.ENCRYPTION_AXOLOTL); + this.file.setKey(transportMessage.getKey()); + this.file.setIv(transportMessage.getIv()); + message.setAxolotlFingerprint(transportMessage.getFingerprint()); + } else { + Log.d(Config.LOGTAG,"could not process KeyTransportMessage"); + } + } else if (message.getEncryption() == Message.ENCRYPTION_OTR) { byte[] key = conversation.getSymmetricKey(); if (key == null) { this.sendCancel(); this.fail(); return; } else { - this.file.setKey(key); + this.file.setKeyAndIv(key); } } + this.mFileOutputStream = AbstractConnectionManager.createOutputStream(this.file,message.getEncryption() == Message.ENCRYPTION_AXOLOTL); this.file.setExpectedSize(size); + Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize()); } else { this.sendCancel(); this.fail(); @@ -364,19 +410,30 @@ public class JingleConnection implements Transferable { Content content = new Content(this.contentCreator, this.contentName); if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { content.setTransportId(this.transportId); - this.file = this.mXmppConnectionService.getFileBackend().getFile( - message, false); + this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false); + Pair pair; if (message.getEncryption() == Message.ENCRYPTION_OTR) { Conversation conversation = this.message.getConversation(); if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key"); cancel(); } + this.file.setKeyAndIv(conversation.getSymmetricKey()); + pair = AbstractConnectionManager.createInputStream(this.file,false); + this.file.setExpectedSize(pair.second); content.setFileOffer(this.file, true); - this.file.setKey(conversation.getSymmetricKey()); + } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + this.file.setKey(mXmppAxolotlMessage.getInnerKey()); + this.file.setIv(mXmppAxolotlMessage.getIV()); + pair = AbstractConnectionManager.createInputStream(this.file,true); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, false).addChild(mXmppAxolotlMessage.toElement()); } else { + pair = AbstractConnectionManager.createInputStream(this.file,false); + this.file.setExpectedSize(pair.second); content.setFileOffer(this.file, false); } + this.mFileInputStream = pair.first; this.transportId = this.mJingleConnectionManager.nextRandomId(); content.setTransportId(this.transportId); content.socks5transport().setChildren(getCandidatesAsElements()); @@ -748,6 +805,8 @@ public class JingleConnection implements Transferable { if (this.transport != null && this.transport instanceof JingleInbandTransport) { this.transport.disconnect(); } + FileBackend.close(mFileInputStream); + FileBackend.close(mFileOutputStream); if (this.message != null) { if (this.responder.equals(account.getJid())) { this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED)); 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 11055b8f..ab7ab73b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java @@ -93,7 +93,7 @@ public class JingleInbandTransport extends JingleTransport { digest.reset(); file.getParentFile().mkdirs(); file.createNewFile(); - this.fileOutputStream = createOutputStream(file); + this.fileOutputStream = connection.getFileOutputStream(); if (this.fileOutputStream == null) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream"); callback.onFileTransferAborted(); @@ -112,15 +112,11 @@ public class JingleInbandTransport extends JingleTransport { this.onFileTransmissionStatusChanged = callback; this.file = file; try { - if (this.file.getKey() != null) { - this.remainingSize = (this.file.getSize() / 16 + 1) * 16; - } else { - this.remainingSize = this.file.getSize(); - } + this.remainingSize = this.file.getExpectedSize(); this.fileSize = this.remainingSize; this.digest = MessageDigest.getInstance("SHA-1"); this.digest.reset(); - fileInputStream = createInputStream(this.file); + fileInputStream = connection.getFileInputStream(); if (fileInputStream == null) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream"); callback.onFileTransferAborted(); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java index 55039070..7545dd64 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -106,13 +106,13 @@ public class JingleSocks5Transport extends JingleTransport { try { MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.reset(); - fileInputStream = createInputStream(file); //file.createInputStream(); + fileInputStream = connection.getFileInputStream(); if (fileInputStream == null) { Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream"); callback.onFileTransferAborted(); return; } - long size = file.getSize(); + long size = file.getExpectedSize(); long transmitted = 0; int count; byte[] buffer = new byte[8192]; @@ -157,7 +157,7 @@ public class JingleSocks5Transport extends JingleTransport { socket.setSoTimeout(30000); file.getParentFile().mkdirs(); file.createNewFile(); - fileOutputStream = createOutputStream(file); + fileOutputStream = connection.getFileOutputStream(); if (fileOutputStream == null) { callback.onFileTransferAborted(); Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream"); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java index 1219794f..b3211158 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java @@ -1,6 +1,13 @@ package eu.siacs.conversations.xmpp.jingle; import android.util.Log; +import android.util.Pair; + +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -31,58 +38,4 @@ public abstract class JingleTransport { final OnFileTransmissionStatusChanged callback); public abstract void disconnect(); - - protected InputStream createInputStream(DownloadableFile file) { - FileInputStream is; - try { - is = new FileInputStream(file); - if (file.getKey() == null) { - return is; - } - } catch (FileNotFoundException e) { - return null; - } - try { - IvParameterSpec ips = new IvParameterSpec(file.getIv()); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); - Log.d(Config.LOGTAG, "opening encrypted input stream"); - return new CipherInputStream(is, cipher); - } catch (InvalidKeyException e) { - return null; - } catch (NoSuchAlgorithmException e) { - return null; - } catch (NoSuchPaddingException e) { - return null; - } catch (InvalidAlgorithmParameterException e) { - return null; - } - } - - protected OutputStream createOutputStream(DownloadableFile file) { - FileOutputStream os; - try { - os = new FileOutputStream(file); - if (file.getKey() == null) { - return os; - } - } catch (FileNotFoundException e) { - return null; - } - try { - IvParameterSpec ips = new IvParameterSpec(file.getIv()); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); - Log.d(Config.LOGTAG, "opening encrypted output stream"); - return new CipherOutputStream(os, cipher); - } catch (InvalidKeyException e) { - return null; - } catch (NoSuchAlgorithmException e) { - return null; - } catch (NoSuchPaddingException e) { - return null; - } catch (InvalidAlgorithmParameterException e) { - return null; - } - } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java index bcadbe77..f752cc5d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java @@ -25,17 +25,18 @@ public class Content extends Element { this.transportId = sid; } - public void setFileOffer(DownloadableFile actualFile, boolean otr) { + public Element setFileOffer(DownloadableFile actualFile, boolean otr) { Element description = this.addChild("description", "urn:xmpp:jingle:apps:file-transfer:3"); Element offer = description.addChild("offer"); Element file = offer.addChild("file"); - file.addChild("size").setContent(Long.toString(actualFile.getSize())); + file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize())); if (otr) { file.addChild("name").setContent(actualFile.getName() + ".otr"); } else { file.addChild("name").setContent(actualFile.getName()); } + return file; } public Element getFileOffer() { -- cgit v1.2.3 From 6cd9383e53900e03d324b227c0f01b1537881148 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Sat, 1 Aug 2015 18:27:52 +0200 Subject: Let UNTRUSTED/UNDECIDED keys become INACTIVE --- .../crypto/axolotl/AxolotlService.java | 30 +++++--- .../crypto/axolotl/SQLiteAxolotlStore.java | 64 ++---------------- .../crypto/axolotl/XmppAxolotlSession.java | 79 +++++++++++++++++++--- .../eu/siacs/conversations/entities/Message.java | 4 +- .../conversations/persistance/DatabaseBackend.java | 19 +++--- .../conversations/ui/ConversationActivity.java | 4 +- .../siacs/conversations/ui/TrustKeysActivity.java | 18 ++--- .../eu/siacs/conversations/ui/XmppActivity.java | 30 ++++---- .../conversations/ui/adapter/MessageAdapter.java | 6 +- 9 files changed, 138 insertions(+), 116 deletions(-) (limited to 'src/main/java/eu') 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 7a92a1f7..255939a4 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -191,11 +191,11 @@ public class AxolotlService { return axolotlStore.getIdentityKeyPair().getPublicKey(); } - public Set getKeysWithTrust(SQLiteAxolotlStore.Trust trust) { + public Set getKeysWithTrust(XmppAxolotlSession.Trust trust) { return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust); } - public Set getKeysWithTrust(SQLiteAxolotlStore.Trust trust, Contact contact) { + public Set getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact) { return axolotlStore.getContactKeysWithTrust(contact.getJid().toBareJid().toString(), trust); } @@ -241,8 +241,8 @@ public class AxolotlService { } private void setTrustOnSessions(final Jid jid, @NonNull final Set deviceIds, - final SQLiteAxolotlStore.Trust from, - final SQLiteAxolotlStore.Trust to) { + final XmppAxolotlSession.Trust from, + final XmppAxolotlSession.Trust to) { for (Integer deviceId : deviceIds) { AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId); XmppAxolotlSession session = sessions.get(address); @@ -267,11 +267,19 @@ public class AxolotlService { } Set expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString())); expiredDevices.removeAll(deviceIds); - setTrustOnSessions(jid, expiredDevices, SQLiteAxolotlStore.Trust.TRUSTED, - SQLiteAxolotlStore.Trust.INACTIVE); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED, + XmppAxolotlSession.Trust.INACTIVE_TRUSTED); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNDECIDED, + XmppAxolotlSession.Trust.INACTIVE_UNDECIDED); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNTRUSTED, + XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED); Set newDevices = new HashSet<>(deviceIds); - setTrustOnSessions(jid, newDevices, SQLiteAxolotlStore.Trust.INACTIVE, - SQLiteAxolotlStore.Trust.TRUSTED); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED, + XmppAxolotlSession.Trust.TRUSTED); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNDECIDED, + XmppAxolotlSession.Trust.UNDECIDED); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED, + XmppAxolotlSession.Trust.UNTRUSTED); this.deviceIds.put(jid, deviceIds); mXmppConnectionService.keyStatusUpdated(); publishOwnDeviceIdIfNeeded(); @@ -291,7 +299,7 @@ public class AxolotlService { } public void purgeKey(IdentityKey identityKey) { - axolotlStore.setFingerprintTrust(identityKey.getFingerprint().replaceAll("\\s", ""), SQLiteAxolotlStore.Trust.COMPROMISED); + axolotlStore.setFingerprintTrust(identityKey.getFingerprint().replaceAll("\\s", ""), XmppAxolotlSession.Trust.COMPROMISED); } public void publishOwnDeviceIdIfNeeded() { @@ -419,11 +427,11 @@ public class AxolotlService { (deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty()); } - public SQLiteAxolotlStore.Trust getFingerprintTrust(String fingerprint) { + public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) { return axolotlStore.getFingerprintTrust(fingerprint); } - public void setFingerprintTrust(String fingerprint, SQLiteAxolotlStore.Trust trust) { + public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) { axolotlStore.setFingerprintTrust(fingerprint, trust); } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java index 0c9c4e65..190eb88a 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java @@ -15,9 +15,7 @@ import org.whispersystems.libaxolotl.state.SessionRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.util.KeyHelper; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; import eu.siacs.conversations.Config; @@ -51,64 +49,14 @@ public class SQLiteAxolotlStore implements AxolotlStore { private int localRegistrationId; private int currentPreKeyId = 0; - private final LruCache trustCache = - new LruCache(NUM_TRUSTS_TO_CACHE) { + private final LruCache trustCache = + new LruCache(NUM_TRUSTS_TO_CACHE) { @Override - protected Trust create(String fingerprint) { + protected XmppAxolotlSession.Trust create(String fingerprint) { return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint); } }; - public enum Trust { - UNDECIDED(0), - TRUSTED(1), - UNTRUSTED(2), - COMPROMISED(3), - INACTIVE(4); - - private static final Map trustsByValue = new HashMap<>(); - - static { - for (Trust trust : Trust.values()) { - trustsByValue.put(trust.getCode(), trust); - } - } - - private final int code; - - Trust(int code) { - this.code = code; - } - - public int getCode() { - return this.code; - } - - public String toString() { - switch (this) { - case UNDECIDED: - return "Trust undecided " + getCode(); - case TRUSTED: - return "Trusted " + getCode(); - case COMPROMISED: - return "Compromised " + getCode(); - case INACTIVE: - return "Inactive " + getCode(); - case UNTRUSTED: - default: - return "Untrusted " + getCode(); - } - } - - public static Trust fromBoolean(Boolean trusted) { - return trusted ? TRUSTED : UNTRUSTED; - } - - public static Trust fromCode(int code) { - return trustsByValue.get(code); - } - } - private static IdentityKeyPair generateIdentityKeyPair() { Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl IdentityKeyPair..."); ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); @@ -258,16 +206,16 @@ public class SQLiteAxolotlStore implements AxolotlStore { return true; } - public Trust getFingerprintTrust(String fingerprint) { + public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) { return (fingerprint == null)? null : trustCache.get(fingerprint); } - public void setFingerprintTrust(String fingerprint, Trust trust) { + public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) { mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust); trustCache.remove(fingerprint); } - public Set getContactKeysWithTrust(String bareJid, Trust trust) { + public Set getContactKeysWithTrust(String bareJid, XmppAxolotlSession.Trust trust) { return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust); } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index 46004a1a..c4053854 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -18,6 +18,9 @@ import org.whispersystems.libaxolotl.protocol.CiphertextMessage; import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; import org.whispersystems.libaxolotl.protocol.WhisperMessage; +import java.util.HashMap; +import java.util.Map; + import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; @@ -30,6 +33,62 @@ public class XmppAxolotlSession { private Integer preKeyId = null; private boolean fresh = true; + public enum Trust { + UNDECIDED(0), + TRUSTED(1), + UNTRUSTED(2), + COMPROMISED(3), + INACTIVE_TRUSTED(4), + INACTIVE_UNDECIDED(5), + INACTIVE_UNTRUSTED(6); + + private static final Map trustsByValue = new HashMap<>(); + + static { + for (Trust trust : Trust.values()) { + trustsByValue.put(trust.getCode(), trust); + } + } + + private final int code; + + Trust(int code) { + this.code = code; + } + + public int getCode() { + return this.code; + } + + public String toString() { + switch (this) { + case UNDECIDED: + return "Trust undecided " + getCode(); + case TRUSTED: + return "Trusted " + getCode(); + case COMPROMISED: + return "Compromised " + getCode(); + case INACTIVE_TRUSTED: + return "Inactive (Trusted)" + getCode(); + case INACTIVE_UNDECIDED: + return "Inactive (Undecided)" + getCode(); + case INACTIVE_UNTRUSTED: + return "Inactive (Untrusted)" + getCode(); + case UNTRUSTED: + default: + return "Untrusted " + getCode(); + } + } + + public static Trust fromBoolean(Boolean trusted) { + return trusted ? TRUSTED : UNTRUSTED; + } + + public static Trust fromCode(int code) { + return trustsByValue.get(code); + } + } + public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) { this(account, store, remoteAddress); this.fingerprint = fingerprint; @@ -67,21 +126,21 @@ public class XmppAxolotlSession { this.fresh = false; } - protected void setTrust(SQLiteAxolotlStore.Trust trust) { + protected void setTrust(Trust trust) { sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust); } - protected SQLiteAxolotlStore.Trust getTrust() { - SQLiteAxolotlStore.Trust trust = sqLiteAxolotlStore.getFingerprintTrust(fingerprint); - return (trust == null) ? SQLiteAxolotlStore.Trust.UNDECIDED : trust; + protected Trust getTrust() { + Trust trust = sqLiteAxolotlStore.getFingerprintTrust(fingerprint); + return (trust == null) ? Trust.UNDECIDED : trust; } @Nullable public byte[] processReceiving(byte[] encryptedKey) { byte[] plaintext = null; - SQLiteAxolotlStore.Trust trust = getTrust(); + Trust trust = getTrust(); switch (trust) { - case INACTIVE: + case INACTIVE_TRUSTED: case UNDECIDED: case UNTRUSTED: case TRUSTED: @@ -110,8 +169,8 @@ public class XmppAxolotlSession { Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); } - if (plaintext != null && trust == SQLiteAxolotlStore.Trust.INACTIVE) { - setTrust(SQLiteAxolotlStore.Trust.TRUSTED); + if (plaintext != null && trust == Trust.INACTIVE_TRUSTED) { + setTrust(Trust.TRUSTED); } break; @@ -126,8 +185,8 @@ public class XmppAxolotlSession { @Nullable public byte[] processSending(@NonNull byte[] outgoingMessage) { - SQLiteAxolotlStore.Trust trust = getTrust(); - if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { + Trust trust = getTrust(); + if (trust == Trust.TRUSTED) { CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); return ciphertextMessage.serialize(); } else { diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 6c2a1cc0..0eff99cf 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -8,7 +8,7 @@ import java.net.URL; import java.util.Arrays; import eu.siacs.conversations.Config; -import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.UIHelper; @@ -707,7 +707,7 @@ public class Message extends AbstractEntity { public boolean isTrusted() { return conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint) - == SQLiteAxolotlStore.Trust.TRUSTED; + == XmppAxolotlSession.Trust.TRUSTED; } private int getPreviousEncryption() { diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 8ac2884e..9fe47512 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -28,6 +28,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -844,7 +845,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { return loadIdentityKeys(account, name, null); } - public Set loadIdentityKeys(Account account, String name, SQLiteAxolotlStore.Trust trust) { + public Set loadIdentityKeys(Account account, String name, XmppAxolotlSession.Trust trust) { Set identityKeys = new HashSet<>(); Cursor cursor = getIdentityKeyCursor(account, name, false); @@ -870,7 +871,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] args = { account.getUuid(), name, - String.valueOf(SQLiteAxolotlStore.Trust.TRUSTED.getCode()) + String.valueOf(XmppAxolotlSession.Trust.TRUSTED.getCode()) }; return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME, SQLiteAxolotlStore.ACCOUNT + " = ?" @@ -881,10 +882,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, SQLiteAxolotlStore.Trust.UNDECIDED); + storeIdentityKey(account, name, own, fingerprint, base64Serialized, XmppAxolotlSession.Trust.UNDECIDED); } - private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, SQLiteAxolotlStore.Trust trusted) { + private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, XmppAxolotlSession.Trust trusted) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); @@ -896,19 +897,19 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); } - public SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String fingerprint) { + public XmppAxolotlSession.Trust isIdentityKeyTrusted(Account account, String fingerprint) { Cursor cursor = getIdentityKeyCursor(account, fingerprint); - SQLiteAxolotlStore.Trust trust = null; + XmppAxolotlSession.Trust trust = null; if (cursor.getCount() > 0) { cursor.moveToFirst(); int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED)); - trust = SQLiteAxolotlStore.Trust.fromCode(trustValue); + trust = XmppAxolotlSession.Trust.fromCode(trustValue); } cursor.close(); return trust; } - public boolean setIdentityKeyTrust(Account account, String fingerprint, SQLiteAxolotlStore.Trust trust) { + public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) { SQLiteDatabase db = this.getWritableDatabase(); String[] selectionArgs = { account.getUuid(), @@ -928,7 +929,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), SQLiteAxolotlStore.Trust.TRUSTED); + storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED); } public void recreateAxolotlDb() { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index f34e1a55..7cd04855 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -38,7 +38,7 @@ import de.timroes.android.listview.EnhancedListView; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.AxolotlService; -import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore.Trust; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Contact; @@ -1260,7 +1260,7 @@ public class ConversationActivity extends XmppActivity protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) { AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService(); - boolean hasPendingKeys = !axolotlService.getKeysWithTrust(Trust.UNDECIDED, + boolean hasPendingKeys = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, mSelectedConversation.getContact()).isEmpty() || !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty(); boolean hasNoTrustedKeys = axolotlService.getNumTrustedKeys(mSelectedConversation.getContact()) == 0; diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index d5959b7a..37ddf590 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -16,7 +16,7 @@ import java.util.Map; import java.util.Set; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore.Trust; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -119,7 +119,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate for(final IdentityKey identityKey : ownKeysToTrust.keySet()) { hasOwnKeys = true; addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey, false, - Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false, + XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false, new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @@ -135,7 +135,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate for(final IdentityKey identityKey : foreignKeysToTrust.keySet()) { hasForeignKeys = true; addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey, false, - Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false, + XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false, new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @@ -171,11 +171,11 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate } private void getFingerprints(final Account account) { - Set ownKeysSet = account.getAxolotlService().getKeysWithTrust(Trust.UNDECIDED); - Set foreignKeysSet = account.getAxolotlService().getKeysWithTrust(Trust.UNDECIDED, contact); + Set ownKeysSet = account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED); + Set foreignKeysSet = account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, contact); if (hasNoTrustedKeys) { - ownKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(Trust.UNTRUSTED)); - foreignKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(Trust.UNTRUSTED, contact)); + ownKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED)); + foreignKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact)); } for(final IdentityKey identityKey : ownKeysSet) { if(!ownKeysToTrust.containsKey(identityKey)) { @@ -226,12 +226,12 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate for(IdentityKey identityKey:ownKeysToTrust.keySet()) { contact.getAccount().getAxolotlService().setFingerprintTrust( identityKey.getFingerprint().replaceAll("\\s", ""), - Trust.fromBoolean(ownKeysToTrust.get(identityKey))); + XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(identityKey))); } for(IdentityKey identityKey:foreignKeysToTrust.keySet()) { contact.getAccount().getAxolotlService().setFingerprintTrust( identityKey.getFingerprint().replaceAll("\\s", ""), - Trust.fromBoolean(foreignKeysToTrust.get(identityKey))); + XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(identityKey))); } } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 64ead283..3a163ba4 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -70,7 +70,7 @@ import java.util.concurrent.RejectedExecutionException; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -615,24 +615,22 @@ public abstract class XmppActivity extends Activity { protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey, boolean highlight) { final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); - final SQLiteAxolotlStore.Trust trust = account.getAxolotlService() + final XmppAxolotlSession.Trust trust = account.getAxolotlService() .getFingerprintTrust(fingerprint); return addFingerprintRowWithListeners(keys, account, identityKey, highlight, trust, true, new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked != (trust == SQLiteAxolotlStore.Trust.TRUSTED)) { - account.getAxolotlService().setFingerprintTrust(fingerprint, - (isChecked) ? SQLiteAxolotlStore.Trust.TRUSTED : - SQLiteAxolotlStore.Trust.UNTRUSTED); - } + account.getAxolotlService().setFingerprintTrust(fingerprint, + (isChecked) ? XmppAxolotlSession.Trust.TRUSTED : + XmppAxolotlSession.Trust.UNTRUSTED); } }, new View.OnClickListener() { @Override public void onClick(View v) { account.getAxolotlService().setFingerprintTrust(fingerprint, - SQLiteAxolotlStore.Trust.UNTRUSTED); + XmppAxolotlSession.Trust.UNTRUSTED); v.setEnabled(true); } } @@ -643,12 +641,12 @@ public abstract class XmppActivity extends Activity { protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account, final IdentityKey identityKey, boolean highlight, - SQLiteAxolotlStore.Trust trust, + XmppAxolotlSession.Trust trust, boolean showTag, CompoundButton.OnCheckedChangeListener onCheckedChangeListener, View.OnClickListener onClickListener) { - if (trust == SQLiteAxolotlStore.Trust.COMPROMISED) { + if (trust == XmppAxolotlSession.Trust.COMPROMISED) { return false; } View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false); @@ -669,7 +667,7 @@ public abstract class XmppActivity extends Activity { switch (trust) { case UNTRUSTED: case TRUSTED: - trustToggle.setChecked(trust == SQLiteAxolotlStore.Trust.TRUSTED, false); + trustToggle.setChecked(trust == XmppAxolotlSession.Trust.TRUSTED, false); trustToggle.setEnabled(true); key.setTextColor(getPrimaryTextColor()); keyType.setTextColor(getSecondaryTextColor()); @@ -680,7 +678,15 @@ public abstract class XmppActivity extends Activity { key.setTextColor(getPrimaryTextColor()); keyType.setTextColor(getSecondaryTextColor()); break; - case INACTIVE: + case INACTIVE_UNTRUSTED: + case INACTIVE_UNDECIDED: + trustToggle.setOnClickListener(null); + trustToggle.setChecked(false, false); + trustToggle.setEnabled(false); + key.setTextColor(getTertiaryTextColor()); + keyType.setTextColor(getTertiaryTextColor()); + break; + case INACTIVE_TRUSTED: trustToggle.setOnClickListener(null); trustToggle.setChecked(true, false); trustToggle.setEnabled(false); 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 76da42c4..aec11b76 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -27,7 +27,7 @@ import android.widget.Toast; import java.util.List; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -169,11 +169,11 @@ public class MessageAdapter extends ArrayAdapter { } else { viewHolder.indicator.setVisibility(View.VISIBLE); if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { - SQLiteAxolotlStore.Trust trust = message.getConversation() + XmppAxolotlSession.Trust trust = message.getConversation() .getAccount().getAxolotlService().getFingerprintTrust( message.getAxolotlFingerprint()); - if(trust == null || trust != SQLiteAxolotlStore.Trust.TRUSTED) { + if(trust == null || trust != XmppAxolotlSession.Trust.TRUSTED) { viewHolder.indicator.setColorFilter(activity.getWarningTextColor()); viewHolder.indicator.setAlpha(1.0f); } else { -- cgit v1.2.3 From f9dec7cf8648ff49bb8ea3b706701b25246309bf Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 1 Aug 2015 22:23:58 +0200 Subject: fixed calculated file size in http slot request --- .../conversations/http/HttpUploadConnection.java | 28 ++++++++++------------ 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index b21d0d41..8c7a69c0 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -47,7 +47,8 @@ public class HttpUploadConnection implements Transferable { private byte[] key = null; private long transmitted = 0; - private int expected = 1; + + private InputStream mFileInputStream; public HttpUploadConnection(HttpConnectionManager httpConnectionManager) { this.mHttpConnectionManager = httpConnectionManager; @@ -71,7 +72,7 @@ public class HttpUploadConnection implements Transferable { @Override public int getProgress() { - return (int) ((((double) transmitted) / expected) * 100); + return (int) ((((double) transmitted) / file.getExpectedSize()) * 100); } @Override @@ -82,18 +83,17 @@ public class HttpUploadConnection implements Transferable { private void fail() { mHttpConnectionManager.finishUploadConnection(this); message.setTransferable(null); - mXmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED); + mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + FileBackend.close(mFileInputStream); } public void init(Message message, boolean delay) { this.message = message; message.setTransferable(this); - mXmppConnectionService.markMessage(message,Message.STATUS_UNSEND); + mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); this.account = message.getConversation().getAccount(); this.file = mXmppConnectionService.getFileBackend().getFile(message, false); - this.file.setExpectedSize(this.file.getSize()); this.delayed = delay; - if (Config.ENCRYPT_ON_HTTP_UPLOADED || message.getEncryption() == Message.ENCRYPTION_AXOLOTL || message.getEncryption() == Message.ENCRYPTION_OTR) { @@ -101,7 +101,9 @@ public class HttpUploadConnection implements Transferable { mXmppConnectionService.getRNG().nextBytes(this.key); this.file.setKeyAndIv(this.key); } - + Pair pair = AbstractConnectionManager.createInputStream(file,true); + this.file.setExpectedSize(pair.second); + this.mFileInputStream = pair.first; Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD); IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file); mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() { @@ -138,7 +140,6 @@ public class HttpUploadConnection implements Transferable { private void upload() { OutputStream os = null; - InputStream is = null; HttpURLConnection connection = null; try { Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString()); @@ -146,25 +147,22 @@ public class HttpUploadConnection implements Transferable { if (connection instanceof HttpsURLConnection) { mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); } - Pair pair = AbstractConnectionManager.createInputStream(file,true); - is = pair.first; - expected = pair.second; connection.setRequestMethod("PUT"); - connection.setFixedLengthStreamingMode(expected); + connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); connection.setDoOutput(true); connection.connect(); os = connection.getOutputStream(); transmitted = 0; int count = -1; byte[] buffer = new byte[4096]; - while (((count = is.read(buffer)) != -1) && !canceled) { + while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) { transmitted += count; os.write(buffer, 0, count); mXmppConnectionService.updateConversationUi(); } os.flush(); os.close(); - is.close(); + mFileInputStream.close(); int code = connection.getResponseCode(); if (code == 200 || code == 201) { Log.d(Config.LOGTAG, "finished uploading file"); @@ -205,7 +203,7 @@ public class HttpUploadConnection implements Transferable { Log.d(Config.LOGTAG,"http upload failed "+e.getMessage()); fail(); } finally { - FileBackend.close(is); + FileBackend.close(mFileInputStream); FileBackend.close(os); if (connection != null) { connection.disconnect(); -- cgit v1.2.3 From 5529337da38fce1f491407eb40480dc7c5782735 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 1 Aug 2015 22:37:17 +0200 Subject: use content-type in http slot request and stick with during upload --- src/main/java/eu/siacs/conversations/generator/IqGenerator.java | 5 ++++- src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 88ff8f46..898d218e 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -254,12 +254,15 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file) { + public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) { IqPacket packet = new IqPacket(IqPacket.TYPE.GET); packet.setTo(host); Element request = packet.addChild("request",Xmlns.HTTP_UPLOAD); request.addChild("filename").setContent(file.getName()); request.addChild("size").setContent(String.valueOf(file.getExpectedSize())); + if (mime != null) { + request.addChild("content-type", mime); + } return packet; } } diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index 8c7a69c0..768915a9 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -41,6 +41,7 @@ public class HttpUploadConnection implements Transferable { private Account account; private DownloadableFile file; private Message message; + private String mime; private URL mGetUrl; private URL mPutUrl; @@ -93,6 +94,7 @@ public class HttpUploadConnection implements Transferable { mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); this.account = message.getConversation().getAccount(); this.file = mXmppConnectionService.getFileBackend().getFile(message, false); + this.mime = this.file.getMimeType(); this.delayed = delay; if (Config.ENCRYPT_ON_HTTP_UPLOADED || message.getEncryption() == Message.ENCRYPTION_AXOLOTL @@ -105,7 +107,7 @@ public class HttpUploadConnection implements Transferable { this.file.setExpectedSize(pair.second); this.mFileInputStream = pair.first; Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD); - IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file); + IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file,mime); mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -149,6 +151,7 @@ public class HttpUploadConnection implements Transferable { } connection.setRequestMethod("PUT"); connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); + connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime); connection.setDoOutput(true); connection.connect(); os = connection.getOutputStream(); -- cgit v1.2.3 From a6bbe4d4ce47bcf662e4cebd2fcae361ffad702e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 1 Aug 2015 23:37:41 +0200 Subject: lets try jpeg for a while --- src/main/java/eu/siacs/conversations/Config.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index d79a12f2..bef00d04 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -20,7 +20,7 @@ public final class Config { public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP; public static final int IMAGE_SIZE = 1920; - public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.WEBP; + public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG; public static final int IMAGE_QUALITY = 75; public static final int MESSAGE_MERGE_WINDOW = 20; -- cgit v1.2.3 From c617cf6ef8ad0b00523a40f886ea64afe47b1712 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 3 Aug 2015 22:58:17 +0200 Subject: added Config.java varibale to lock account creation to specfic domain --- src/main/java/eu/siacs/conversations/Config.java | 4 ++ .../ui/ConferenceDetailsActivity.java | 10 ++- .../conversations/ui/ContactDetailsActivity.java | 9 ++- .../conversations/ui/EditAccountActivity.java | 75 ++++++++++++++++------ .../ui/PublishProfilePictureActivity.java | 9 ++- .../ui/StartConversationActivity.java | 27 +++++--- .../conversations/ui/adapter/AccountAdapter.java | 7 +- 7 files changed, 108 insertions(+), 33 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index bef00d04..493ec3c3 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -8,6 +8,10 @@ public final class Config { public static final String LOGTAG = "conversations"; + + public static final String DOMAIN_LOCK = null; //only allow account creation for this domain + public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox + public static final int PING_MAX_INTERVAL = 300; public static final int PING_MIN_INTERVAL = 30; public static final int PING_TIMEOUT = 10; diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 94777931..12b66c35 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.entities.Account; @@ -421,8 +422,13 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers private void updateView() { final MucOptions mucOptions = mConversation.getMucOptions(); final User self = mucOptions.getSelf(); - mAccountJid.setText(getString(R.string.using_account, mConversation - .getAccount().getJid().toBareJid())); + String account; + if (Config.DOMAIN_LOCK != null) { + account = mConversation.getAccount().getJid().getLocalpart(); + } else { + account = mConversation.getAccount().getJid().toBareJid().toString(); + } + mAccountJid.setText(getString(R.string.using_account, account)); mYourPhoto.setImageBitmap(avatarService().get(mConversation.getAccount(), getPixel(48))); setTitle(mConversation.getName()); mFullJid.setText(mConversation.getJid().toBareJid().toString()); diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 4c3923bb..d98e9164 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -33,6 +33,7 @@ import org.whispersystems.libaxolotl.IdentityKey; import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.entities.Account; @@ -359,7 +360,13 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } else { contactJidTv.setText(contact.getJid().toString()); } - accountJidTv.setText(getString(R.string.using_account, contact.getAccount().getJid().toBareJid())); + String account; + if (Config.DOMAIN_LOCK != null) { + account = contact.getAccount().getJid().getLocalpart(); + } else { + account = contact.getAccount().getJid().toBareJid().toString(); + } + accountJidTv.setText(getString(R.string.using_account, account)); badge.setImageBitmap(avatarService().get(contact, getPixel(72))); badge.setOnClickListener(this.onBadgeClick); diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index a1c28df8..cfe3c38e 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -63,6 +63,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private TextView mSessionEst; private TextView mOtrFingerprint; private TextView mAxolotlFingerprint; + private TextView mAccountJidLabel; private ImageView mAvatar; private RelativeLayout mOtrFingerprintBox; private RelativeLayout mAxolotlFingerprintBox; @@ -87,17 +88,34 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate xmppConnectionService.updateAccount(mAccount); return; } - final boolean registerNewAccount = mRegisterNew.isChecked(); + final boolean registerNewAccount = mRegisterNew.isChecked() && !Config.DISALLOW_REGISTRATION_IN_UI; + if (Config.DOMAIN_LOCK != null && mAccountJid.getText().toString().contains("@")) { + mAccountJid.setError(getString(R.string.invalid_username)); + mAccountJid.requestFocus(); + return; + } final Jid jid; try { - jid = Jid.fromString(mAccountJid.getText().toString()); + if (Config.DOMAIN_LOCK != null) { + jid = Jid.fromParts(mAccountJid.getText().toString(),Config.DOMAIN_LOCK,null); + } else { + jid = Jid.fromString(mAccountJid.getText().toString()); + } } catch (final InvalidJidException e) { - mAccountJid.setError(getString(R.string.invalid_jid)); + if (Config.DOMAIN_LOCK != null) { + mAccountJid.setError(getString(R.string.invalid_username)); + } else { + mAccountJid.setError(getString(R.string.invalid_jid)); + } mAccountJid.requestFocus(); return; } if (jid.isDomainJid()) { - mAccountJid.setError(getString(R.string.invalid_jid)); + if (Config.DOMAIN_LOCK != null) { + mAccountJid.setError(getString(R.string.invalid_username)); + } else { + mAccountJid.setError(getString(R.string.invalid_jid)); + } mAccountJid.requestFocus(); return; } @@ -123,13 +141,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); xmppConnectionService.updateAccount(mAccount); } else { - try { - if (xmppConnectionService.findAccountByJid(Jid.fromString(mAccountJid.getText().toString())) != null) { - mAccountJid.setError(getString(R.string.account_already_exists)); - mAccountJid.requestFocus(); - return; - } - } catch (final InvalidJidException e) { + if (xmppConnectionService.findAccountByJid(jid) != null) { + mAccountJid.setError(getString(R.string.account_already_exists)); + mAccountJid.requestFocus(); return; } mAccount = new Account(jid.toBareJid(), password); @@ -285,10 +299,17 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } protected boolean accountInfoEdited() { - return this.mAccount != null && (!this.mAccount.getJid().toBareJid().toString().equals( - this.mAccountJid.getText().toString()) - || !this.mAccount.getPassword().equals( - this.mPassword.getText().toString())); + if (this.mAccount == null) { + return false; + } + final String unmodified; + if (Config.DOMAIN_LOCK != null) { + unmodified = this.mAccount.getJid().getLocalpart(); + } else { + unmodified = this.mAccount.getJid().toBareJid().toString(); + } + return !unmodified.equals(this.mAccountJid.getText().toString()) || + !this.mAccount.getPassword().equals(this.mPassword.getText().toString()); } @Override @@ -306,6 +327,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate setContentView(R.layout.activity_edit_account); this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid); this.mAccountJid.addTextChangedListener(this.mTextWatcher); + this.mAccountJidLabel = (TextView) findViewById(R.id.account_jid_label); + if (Config.DOMAIN_LOCK != null) { + this.mAccountJidLabel.setText(R.string.username); + this.mAccountJid.setHint(R.string.username_hint); + } this.mPassword = (EditText) findViewById(R.id.account_password); this.mPassword.addTextChangedListener(this.mTextWatcher); this.mPasswordConfirm = (EditText) findViewById(R.id.account_password_confirm); @@ -348,6 +374,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } }; this.mRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword); + if (Config.DISALLOW_REGISTRATION_IN_UI) { + this.mRegisterNew.setVisibility(View.GONE); + } } @Override @@ -406,9 +435,6 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate @Override protected void onBackendConnected() { - final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this, - android.R.layout.simple_list_item_1, - xmppConnectionService.getKnownHosts()); if (this.jidToEdit != null) { this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit); updateAccountInformation(true); @@ -421,7 +447,12 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mCancelButton.setEnabled(false); this.mCancelButton.setTextColor(getSecondaryTextColor()); } - this.mAccountJid.setAdapter(mKnownHostsAdapter); + if (Config.DOMAIN_LOCK == null) { + final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this, + android.R.layout.simple_list_item_1, + xmppConnectionService.getKnownHosts()); + this.mAccountJid.setAdapter(mKnownHostsAdapter); + } updateSaveButton(); } @@ -451,7 +482,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private void updateAccountInformation(boolean init) { if (init) { - this.mAccountJid.setText(this.mAccount.getJid().toBareJid().toString()); + if (Config.DOMAIN_LOCK != null) { + this.mAccountJid.setText(this.mAccount.getJid().getLocalpart()); + } else { + this.mAccountJid.setText(this.mAccount.getJid().toBareJid().toString()); + } this.mPassword.setText(this.mAccount.getPassword()); } if (this.jidToEdit != null) { diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index 2f6d765d..e01490f9 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -13,6 +13,7 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.utils.PhoneHelper; @@ -192,7 +193,13 @@ public class PublishProfilePictureActivity extends XmppActivity { } else { loadImageIntoPreview(avatarUri); } - this.accountTextView.setText(this.account.getJid().toBareJid().toString()); + String account; + if (Config.DOMAIN_LOCK != null) { + account = this.account.getJid().getLocalpart(); + } else { + account = this.account.getJid().toBareJid().toString(); + } + this.accountTextView.setText(account); } } diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 68e77af4..74621fc4 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -289,7 +289,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU protected void toggleContactBlock() { final int position = contact_context_id; - BlockContactDialog.show(this, xmppConnectionService, (Contact)contacts.get(position)); + BlockContactDialog.show(this, xmppConnectionService, (Contact) contacts.get(position)); } protected void deleteContact() { @@ -299,7 +299,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.action_delete_contact); builder.setMessage(getString(R.string.remove_contact_text, - contact.getJid())); + contact.getJid())); builder.setPositiveButton(R.string.delete, new OnClickListener() { @Override @@ -319,7 +319,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.delete_bookmark); builder.setMessage(getString(R.string.remove_bookmark_text, - bookmark.getJid())); + bookmark.getJid())); builder.setPositiveButton(R.string.delete, new OnClickListener() { @Override @@ -368,7 +368,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } final Jid accountJid; try { - accountJid = Jid.fromString((String) spinner.getSelectedItem()); + if (Config.DOMAIN_LOCK != null) { + accountJid = Jid.fromParts((String) spinner.getSelectedItem(),Config.DOMAIN_LOCK,null); + } else { + accountJid = Jid.fromString((String) spinner.getSelectedItem()); + } } catch (final InvalidJidException e) { return; } @@ -379,8 +383,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU jid.setError(getString(R.string.invalid_jid)); return; } - final Account account = xmppConnectionService - .findAccountByJid(accountJid); + final Account account = xmppConnectionService.findAccountByJid(accountJid); if (account == null) { dialog.dismiss(); return; @@ -428,7 +431,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } final Jid accountJid; try { - accountJid = Jid.fromString((String) spinner.getSelectedItem()); + if (Config.DOMAIN_LOCK != null) { + accountJid = Jid.fromParts((String) spinner.getSelectedItem(),Config.DOMAIN_LOCK,null); + } else { + accountJid = Jid.fromString((String) spinner.getSelectedItem()); + } } catch (final InvalidJidException e) { return; } @@ -576,7 +583,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU this.mActivatedAccounts.clear(); for (Account account : xmppConnectionService.getAccounts()) { if (account.getStatus() != Account.State.DISABLED) { - this.mActivatedAccounts.add(account.getJid().toBareJid().toString()); + if (Config.DOMAIN_LOCK != null) { + this.mActivatedAccounts.add(account.getJid().getLocalpart()); + } else { + this.mActivatedAccounts.add(account.getJid().toBareJid().toString()); + } } } final Intent intent = getIntent(); 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 ece4ac6b..98250af9 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -11,6 +11,7 @@ import android.widget.TextView; import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.ui.ManageAccountActivity; @@ -35,7 +36,11 @@ public class AccountAdapter extends ArrayAdapter { view = inflater.inflate(R.layout.account_row, parent, false); } TextView jid = (TextView) view.findViewById(R.id.account_jid); - jid.setText(account.getJid().toBareJid().toString()); + if (Config.DOMAIN_LOCK != null) { + jid.setText(account.getJid().getLocalpart()); + } else { + jid.setText(account.getJid().toBareJid().toString()); + } TextView statusView = (TextView) view.findViewById(R.id.account_status); ImageView imageView = (ImageView) view.findViewById(R.id.account_image); imageView.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48))); -- cgit v1.2.3 From a1c43d8fdfd87829a4d2d4e0d1347674f98c79ee Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 5 Aug 2015 18:52:34 +0200 Subject: added config.java variable to hide openpgp as an encryption method --- src/main/java/eu/siacs/conversations/Config.java | 1 + src/main/java/eu/siacs/conversations/ui/ConversationActivity.java | 1 + src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java | 2 ++ 3 files changed, 4 insertions(+) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 493ec3c3..d1cb3cc1 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -11,6 +11,7 @@ public final class Config { public static final String DOMAIN_LOCK = null; //only allow account creation for this domain public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox + public static final boolean HIDE_PGP_IN_UI = false; //some more consumer focused clients might want to disable OpenPGP public static final int PING_MAX_INTERVAL = 300; public static final int PING_MIN_INTERVAL = 30; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 7cd04855..9596041f 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -784,6 +784,7 @@ public class ConversationActivity extends XmppActivity 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); + pgp.setVisible(!Config.HIDE_PGP_IN_UI); if (conversation.getMode() == Conversation.MODE_MULTI) { otr.setEnabled(false); axolotl.setEnabled(false); diff --git a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java index e1189e7a..5b9b7355 100644 --- a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -18,6 +18,7 @@ import android.widget.ListView; import java.util.ArrayList; import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; @@ -81,6 +82,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false); } else { menu.findItem(R.id.mgmt_account_enable).setVisible(false); + menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(!Config.HIDE_PGP_IN_UI); } menu.setHeaderTitle(this.selectedAccount.getJid().toBareJid().toString()); } -- cgit v1.2.3 From 34b22dea48bfc2f932bfdb27439493a71a409260 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 5 Aug 2015 22:22:37 +0200 Subject: Improve TrustKeysActivity slider responsiveness Slider used to skip back on drag-and-drop action. The switch doesn't trigger explicit whole UI refreshes anymore, it now directly adjusts the "done" button's locked status. --- .../siacs/conversations/ui/TrustKeysActivity.java | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index 37ddf590..c5551857 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -124,9 +124,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { ownKeysToTrust.put(identityKey, isChecked); - refreshUi(); - xmppConnectionService.updateAccountUi(); - xmppConnectionService.updateConversationUi(); + // own fingerprints have no impact on locked status. } }, null @@ -140,9 +138,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { foreignKeysToTrust.put(identityKey, isChecked); - refreshUi(); - xmppConnectionService.updateAccountUi(); - xmppConnectionService.updateConversationUi(); + lockOrUnlockAsNeeded(); } }, null @@ -161,11 +157,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate setFetching(); lock(); } else { - if (!hasOtherTrustedKeys && !foreignKeysToTrust.values().contains(true)){ - lock(); - } else { - unlock(); - } + lockOrUnlockAsNeeded(); setDone(); } } @@ -245,6 +237,14 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate mSaveButton.setTextColor(getSecondaryTextColor()); } + private void lockOrUnlockAsNeeded() { + if (!hasOtherTrustedKeys && !foreignKeysToTrust.values().contains(true)){ + lock(); + } else { + unlock(); + } + } + private void setDone() { mSaveButton.setText(getString(R.string.done)); } -- cgit v1.2.3 From 5c0853f30250214b15d7cceb9a0dc34a6c99b7e5 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 6 Aug 2015 13:09:53 +0200 Subject: hide multi-end and otr encryption in conferences --- src/main/java/eu/siacs/conversations/ui/ConversationActivity.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 9596041f..219a4fca 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -396,6 +396,7 @@ public class ConversationActivity extends XmppActivity menuContactDetails.setVisible(false); menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable()); menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite()); + menuSecure.setVisible(!Config.HIDE_PGP_IN_UI); //if pgp is hidden conferences have no choice of encryption } else { menuMucDetails.setVisible(false); } @@ -786,8 +787,8 @@ public class ConversationActivity extends XmppActivity MenuItem axolotl = popup.getMenu().findItem(R.id.encryption_choice_axolotl); pgp.setVisible(!Config.HIDE_PGP_IN_UI); if (conversation.getMode() == Conversation.MODE_MULTI) { - otr.setEnabled(false); - axolotl.setEnabled(false); + otr.setVisible(false); + axolotl.setVisible(false); } else if (!conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) { axolotl.setEnabled(false); } -- cgit v1.2.3 From 6694af8fcadbfdbb2d83e5fffda558478f595e63 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 6 Aug 2015 14:54:37 +0200 Subject: fail old/invalid iq stanzas on bind --- .../java/eu/siacs/conversations/xmpp/XmppConnection.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index c41c5174..decfabf6 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -709,6 +710,7 @@ public class XmppConnection implements Runnable { } catch (final InterruptedException ignored) { } } + clearIqCallbacks(); final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind") .addChild("resource").setContent(account.getResource()); @@ -739,6 +741,17 @@ public class XmppConnection implements Runnable { }); } + private void clearIqCallbacks() { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": clearing iq iq callbacks"); + final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.ERROR); + Iterator>> iterator = this.packetCallbacks.entrySet().iterator(); + while(iterator.hasNext()) { + Entry> entry = iterator.next(); + entry.getValue().second.onIqPacketReceived(account,failurePacket); + iterator.remove(); + } + } + private void sendStartSession() { final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET); startSession.addChild("session","urn:ietf:params:xml:ns:xmpp-session"); -- cgit v1.2.3 From 53ce5d223ea15b748c268d0e77c81d93d97706b0 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 6 Aug 2015 20:48:55 +0200 Subject: request server-ACKs for iq stanzas --- .../siacs/conversations/xmpp/XmppConnection.java | 70 ++++++++++++---------- 1 file changed, 37 insertions(+), 33 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index decfabf6..0ed6e622 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -95,7 +95,7 @@ public class XmppConnection implements Runnable { private String streamId = null; private int smVersion = 3; - private final SparseArray messageReceipts = new SparseArray<>(); + private final SparseArray mStanzaReceipts = new SparseArray<>(); private int stanzasReceived = 0; private int stanzasSent = 0; @@ -341,23 +341,24 @@ public class XmppConnection implements Runnable { + ": session resumed with lost packages"); stanzasSent = serverCount; } else { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": session resumed"); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": session resumed"); } - if (acknowledgedListener != null) { - for (int i = 0; i < messageReceipts.size(); ++i) { - if (serverCount >= messageReceipts.keyAt(i)) { - acknowledgedListener.onMessageAcknowledged( - account, messageReceipts.valueAt(i)); - } + acknowledgeStanzaUpTo(serverCount); + ArrayList failedIqPackets = new ArrayList<>(); + for(int i = 0; i < this.mStanzaReceipts.size(); ++i) { + String id = mStanzaReceipts.valueAt(i); + Pair pair = id == null ? null : this.packetCallbacks.get(id); + if (pair != null) { + failedIqPackets.add(pair.first); } } - messageReceipts.clear(); + mStanzaReceipts.clear(); + Log.d(Config.LOGTAG,"resending "+failedIqPackets.size()+" iq stanza"); + for(IqPacket packet : failedIqPackets) { + sendUnmodifiedIqPacket(packet,null); + } } catch (final NumberFormatException ignored) { } - sendServiceDiscoveryInfo(account.getServer()); - sendServiceDiscoveryInfo(account.getJid().toBareJid()); - sendServiceDiscoveryItems(account.getServer()); sendInitialPing(); } else if (nextTag.isStart("r")) { tagReader.readElement(nextTag); @@ -371,17 +372,7 @@ public class XmppConnection implements Runnable { lastPacketReceived = SystemClock.elapsedRealtime(); try { final int serverSequence = Integer.parseInt(ack.getAttribute("h")); - if (Config.EXTENDED_SM_LOGGING) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + serverSequence); - } - final String msgId = this.messageReceipts.get(serverSequence); - if (msgId != null) { - if (this.acknowledgedListener != null) { - this.acknowledgedListener.onMessageAcknowledged( - account, msgId); - } - this.messageReceipts.remove(serverSequence); - } + acknowledgeStanzaUpTo(serverSequence); } catch (NumberFormatException e) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server send ack without sequence number"); } @@ -409,6 +400,22 @@ public class XmppConnection implements Runnable { } } + private void acknowledgeStanzaUpTo(int serverCount) { + for (int i = 0; i < mStanzaReceipts.size(); ++i) { + if (serverCount >= mStanzaReceipts.keyAt(i)) { + if (Config.EXTENDED_SM_LOGGING) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + mStanzaReceipts.keyAt(i)); + } + String id = mStanzaReceipts.valueAt(i); + if (acknowledgedListener != null) { + acknowledgedListener.onMessageAcknowledged(account, id); + } + mStanzaReceipts.removeAt(i); + i--; + } + } + } + private void sendInitialPing() { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": sending intial ping"); final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); @@ -754,7 +761,7 @@ public class XmppConnection implements Runnable { private void sendStartSession() { final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET); - startSession.addChild("session","urn:ietf:params:xml:ns:xmpp-session"); + startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session"); this.sendUnmodifiedIqPacket(startSession, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -778,7 +785,7 @@ public class XmppConnection implements Runnable { final EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); stanzasSent = 0; - messageReceipts.clear(); + mStanzaReceipts.clear(); } features.carbonsEnabled = false; features.blockListRequested = false; @@ -910,7 +917,7 @@ public class XmppConnection implements Runnable { public void sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { packet.setFrom(account.getJid()); - this.sendUnmodifiedIqPacket(packet,callback); + this.sendUnmodifiedIqPacket(packet, callback); } @@ -920,9 +927,6 @@ public class XmppConnection implements Runnable { packet.setAttribute("id", id); } if (callback != null) { - if (packet.getId() == null) { - packet.setId(nextRandomId()); - } packetCallbacks.put(packet.getId(), new Pair<>(packet, callback)); } this.sendPacket(packet); @@ -947,11 +951,11 @@ public class XmppConnection implements Runnable { ++stanzasSent; } tagWriter.writeStanzaAsync(packet); - if (packet instanceof MessagePacket && packet.getId() != null && getFeatures().sm()) { + if ((packet instanceof MessagePacket || packet instanceof IqPacket) && packet.getId() != null && getFeatures().sm()) { if (Config.EXTENDED_SM_LOGGING) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for message stanza #" + stanzasSent); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for stanza #" + stanzasSent); } - this.messageReceipts.put(stanzasSent, packet.getId()); + this.mStanzaReceipts.put(stanzasSent, packet.getId()); tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion)); } } -- cgit v1.2.3 From 7437d0fe0cb77f5d17c325d6746cc833423369fc Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 7 Aug 2015 12:28:45 +0200 Subject: Increase number of published prekeys for release --- src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/java/eu') 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 255939a4..f3775b79 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -49,7 +49,7 @@ public class AxolotlService { public static final String LOGPREFIX = "AxolotlService"; - public static final int NUM_KEYS_TO_PUBLISH = 10; + public static final int NUM_KEYS_TO_PUBLISH = 100; private final Account account; private final XmppConnectionService mXmppConnectionService; -- cgit v1.2.3 From cac577fa4e0d291981105033d539ece474e16373 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 8 Aug 2015 10:26:36 +0200 Subject: don't request ack for iq stanzas before stream managment is initialized fixes #1322 --- src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 0ed6e622..bf89ba81 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -951,7 +951,7 @@ public class XmppConnection implements Runnable { ++stanzasSent; } tagWriter.writeStanzaAsync(packet); - if ((packet instanceof MessagePacket || packet instanceof IqPacket) && packet.getId() != null && getFeatures().sm()) { + if ((packet instanceof MessagePacket || packet instanceof IqPacket) && packet.getId() != null && this.streamId != null) { if (Config.EXTENDED_SM_LOGGING) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for stanza #" + stanzasSent); } @@ -1024,7 +1024,7 @@ public class XmppConnection implements Runnable { if (tagWriter.isActive()) { tagWriter.finish(); try { - while (!tagWriter.finished()) { + while (!tagWriter.finished() && socket.isConnected()) { Log.d(Config.LOGTAG, "not yet finished"); Thread.sleep(100); } -- cgit v1.2.3 From efdf3b6c1ca0fe807781a2e974d42f8853599f9b Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 8 Aug 2015 13:13:23 +0200 Subject: removed dead code --- src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index bf89ba81..5ac08976 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -81,7 +81,6 @@ public class XmppConnection implements Runnable { private static final int PACKET_IQ = 0; private static final int PACKET_MESSAGE = 1; private static final int PACKET_PRESENCE = 2; - private final Context applicationContext; protected Account account; private final WakeLock wakeLock; private Socket socket; @@ -123,7 +122,6 @@ public class XmppConnection implements Runnable { PowerManager.PARTIAL_WAKE_LOCK, account.getJid().toBareJid().toString()); tagWriter = new TagWriter(); mXmppConnectionService = service; - applicationContext = service.getApplicationContext(); } protected void changeStatus(final Account.State nextStatus) { @@ -531,14 +529,6 @@ public class XmppConnection implements Runnable { tagWriter.writeTag(startTLS); } - private SharedPreferences getPreferences() { - return PreferenceManager.getDefaultSharedPreferences(applicationContext); - } - - private boolean enableLegacySSL() { - return getPreferences().getBoolean("enable_legacy_ssl", false); - } - private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException { tagReader.readTag(); try { -- cgit v1.2.3 From 8b9b74ff7ee863160e0cf2827e2a5a431d4fb90f Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 8 Aug 2015 13:29:21 +0200 Subject: renamed multi-end / axolotl to OMEMO --- src/main/java/eu/siacs/conversations/ui/ConversationFragment.java | 2 +- src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java | 4 ++-- src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java | 2 +- src/main/java/eu/siacs/conversations/ui/XmppActivity.java | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 88504a92..de758efc 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -333,7 +333,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa mEditMessage.setHint(getString(R.string.send_otr_message)); break; case Message.ENCRYPTION_AXOLOTL: - mEditMessage.setHint(getString(R.string.send_axolotl_message)); + mEditMessage.setHint(getString(R.string.send_omemo_message)); break; case Message.ENCRYPTION_PGP: mEditMessage.setHint(getString(R.string.send_pgp_message)); diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index cfe3c38e..b4ed044c 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -577,10 +577,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate @Override public void onClick(final View v) { - if (copyTextToClipboard(axolotlFingerprint, R.string.axolotl_fingerprint)) { + if (copyTextToClipboard(axolotlFingerprint, R.string.omemo_fingerprint)) { Toast.makeText( EditAccountActivity.this, - R.string.toast_message_axolotl_fingerprint, + R.string.toast_message_omemo_fingerprint, Toast.LENGTH_SHORT).show(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index c5551857..cf22416f 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -111,7 +111,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate } private void populateView() { - setTitle(getString(R.string.trust_keys)); + setTitle(getString(R.string.trust_omemo_fingerprints)); ownKeys.removeAllViews(); foreignKeys.removeAllViews(); boolean hasOwnKeys = false; diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 3a163ba4..7734dc11 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -696,15 +696,15 @@ public abstract class XmppActivity extends Activity { } if (showTag) { - keyType.setText(getString(R.string.axolotl_fingerprint)); + keyType.setText(getString(R.string.omemo_fingerprint)); } else { keyType.setVisibility(View.GONE); } if (highlight) { keyType.setTextColor(getResources().getColor(R.color.accent)); - keyType.setText(getString(R.string.axolotl_fingerprint_selected_message)); + keyType.setText(getString(R.string.omemo_fingerprint_selected_message)); } else { - keyType.setText(getString(R.string.axolotl_fingerprint)); + keyType.setText(getString(R.string.omemo_fingerprint)); } key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint())); -- cgit v1.2.3 From b7f00ddac32d8938ae6a2ae94a6ca0255edbef6f Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 8 Aug 2015 17:19:40 +0200 Subject: fixed image preview in notfications for images that arrived over jingle --- .../eu/siacs/conversations/xmpp/jingle/JingleConnection.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src/main/java/eu') 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 e9ca66b7..29aafcce 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -97,15 +97,13 @@ public class JingleConnection implements Transferable { public void onFileTransmitted(DownloadableFile file) { if (responder.equals(account.getJid())) { sendSuccess(); + mXmppConnectionService.getFileBackend().updateFileParams(message); + mXmppConnectionService.databaseBackend.createMessage(message); + mXmppConnectionService.markMessage(message,Message.STATUS_RECEIVED); if (acceptedAutomatically) { message.markUnread(); - JingleConnection.this.mXmppConnectionService - .getNotificationService().push(message); + JingleConnection.this.mXmppConnectionService.getNotificationService().push(message); } - mXmppConnectionService.getFileBackend().updateFileParams(message); - mXmppConnectionService.databaseBackend.createMessage(message); - mXmppConnectionService.markMessage(message, - Message.STATUS_RECEIVED); } else { if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { file.delete(); -- cgit v1.2.3 From b5e90850d88d3fe29387697b8976b52e2e35b1f6 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 10 Aug 2015 12:15:14 +0200 Subject: provide more detailed error toasts for http file download --- .../conversations/http/HttpDownloadConnection.java | 95 +++++++++++++--------- 1 file changed, 58 insertions(+), 37 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 79e4654b..0c1ea503 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -6,6 +6,7 @@ import android.util.Log; import java.io.BufferedInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; @@ -125,6 +126,17 @@ public class HttpDownloadConnection implements Transferable { mXmppConnectionService.updateConversationUi(); } + private void showToastForException(Exception e) { + e.printStackTrace(); + if (e instanceof java.net.UnknownHostException) { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_server_not_found); + } else if (e instanceof java.net.ConnectException) { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_connect); + } else { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_file_not_found); + } + } + private class FileSizeChecker implements Runnable { private boolean interactive = false; @@ -146,7 +158,7 @@ public class HttpDownloadConnection implements Transferable { } catch (IOException e) { Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage()); if (interactive) { - mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host); + showToastForException(e); } cancel(); return; @@ -163,20 +175,23 @@ public class HttpDownloadConnection implements Transferable { } private long retrieveFileSize() throws IOException { - Log.d(Config.LOGTAG,"retrieve file size. interactive:"+String.valueOf(interactive)); - changeStatus(STATUS_CHECKING); - HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); - connection.setRequestMethod("HEAD"); - if (connection instanceof HttpsURLConnection) { - mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); - } - connection.connect(); - String contentLength = connection.getHeaderField("Content-Length"); - if (contentLength == null) { - throw new IOException(); - } try { + Log.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive)); + changeStatus(STATUS_CHECKING); + HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); + connection.setRequestMethod("HEAD"); + if (connection instanceof HttpsURLConnection) { + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); + } + connection.connect(); + String contentLength = connection.getHeaderField("Content-Length"); + connection.disconnect(); + if (contentLength == null) { + throw new IOException(); + } return Long.parseLong(contentLength, 10); + } catch (IOException e) { + throw e; } catch (NumberFormatException e) { throw new IOException(); } @@ -202,37 +217,43 @@ public class HttpDownloadConnection implements Transferable { updateImageBounds(); finish(); } catch (SSLHandshakeException e) { - FileBackend.close(os); changeStatus(STATUS_OFFER); } catch (IOException e) { - FileBackend.close(os); - mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host); + if (interactive) { + showToastForException(e); + } cancel(); } } - private void download() throws IOException { - HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); - if (connection instanceof HttpsURLConnection) { - mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); - } - connection.connect(); - BufferedInputStream is = new BufferedInputStream(connection.getInputStream()); - file.getParentFile().mkdirs(); - file.createNewFile(); - os = AbstractConnectionManager.createOutputStream(file,true); - long transmitted = 0; - long expected = file.getExpectedSize(); - int count = -1; - byte[] buffer = new byte[1024]; - while ((count = is.read(buffer)) != -1) { - transmitted += count; - os.write(buffer, 0, count); - updateProgress((int) ((((double) transmitted) / expected) * 100)); + private void download() throws IOException { + InputStream is = null; + try { + HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); + if (connection instanceof HttpsURLConnection) { + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); + } + connection.connect(); + is = new BufferedInputStream(connection.getInputStream()); + file.getParentFile().mkdirs(); + file.createNewFile(); + os = AbstractConnectionManager.createOutputStream(file, true); + long transmitted = 0; + long expected = file.getExpectedSize(); + int count = -1; + byte[] buffer = new byte[1024]; + while ((count = is.read(buffer)) != -1) { + transmitted += count; + os.write(buffer, 0, count); + updateProgress((int) ((((double) transmitted) / expected) * 100)); + } + os.flush(); + } catch (IOException e) { + throw e; + } finally { + FileBackend.close(os); + FileBackend.close(is); } - os.flush(); - os.close(); - is.close(); } private void updateImageBounds() { -- cgit v1.2.3 From d30515a85acaedb57d0d4308aeb72d96074f729a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 10 Aug 2015 12:55:37 +0200 Subject: report wrong file size in otr encrypted jingle file transfers to be compatible with conversations > 1.6 --- src/main/java/eu/siacs/conversations/Config.java | 2 ++ .../siacs/conversations/services/AbstractConnectionManager.java | 3 ++- .../java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java | 8 ++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index d1cb3cc1..f3cbffe6 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -42,6 +42,8 @@ public final class Config { public static final boolean ENCRYPT_ON_HTTP_UPLOADED = false; + public static final boolean REPORT_WRONG_FILESIZE_IN_OTR_JINGLE = true; + public static final boolean SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON = false; public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; diff --git a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java index b7e7c8d3..dbd7f376 100644 --- a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java @@ -72,7 +72,8 @@ public class AbstractConnectionManager { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); Log.d(Config.LOGTAG, "opening encrypted input stream"); - return new Pair(new CipherInputStream(is, cipher),(size / 16 + 1) * 16); + final int s = Config.REPORT_WRONG_FILESIZE_IN_OTR_JINGLE ? size : (size / 16 + 1) * 16; + return new Pair(new CipherInputStream(is, cipher),s); } } catch (InvalidKeyException e) { return null; 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 29aafcce..d074bf9c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -272,7 +272,7 @@ public class JingleConnection implements Transferable { }); mergeCandidate(candidate); } else { - Log.d(Config.LOGTAG,"no primary candidate of our own was found"); + Log.d(Config.LOGTAG, "no primary candidate of our own was found"); sendInitRequest(); } } @@ -391,7 +391,11 @@ public class JingleConnection implements Transferable { } } this.mFileOutputStream = AbstractConnectionManager.createOutputStream(this.file,message.getEncryption() == Message.ENCRYPTION_AXOLOTL); - this.file.setExpectedSize(size); + if (message.getEncryption() == Message.ENCRYPTION_OTR && Config.REPORT_WRONG_FILESIZE_IN_OTR_JINGLE) { + this.file.setExpectedSize((size / 16 + 1) * 16); + } else { + this.file.setExpectedSize(size); + } Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize()); } else { this.sendCancel(); -- cgit v1.2.3 From fd81491b059983eb9bcafc91eb91055174bce131 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 10 Aug 2015 19:48:36 +0200 Subject: put wake locks on out of band file transfers --- .../conversations/http/HttpDownloadConnection.java | 4 +++ .../conversations/http/HttpUploadConnection.java | 4 +++ .../services/AbstractConnectionManager.java | 7 ++++ .../xmpp/jingle/JingleConnection.java | 5 ++- .../xmpp/jingle/JingleSocks5Transport.java | 37 ++++++++-------------- 5 files changed, 32 insertions(+), 25 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 0c1ea503..0d202bb9 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.http; import android.content.Intent; import android.net.Uri; +import android.os.PowerManager; import android.util.Log; import java.io.BufferedInputStream; @@ -228,7 +229,9 @@ public class HttpDownloadConnection implements Transferable { private void download() throws IOException { InputStream is = null; + PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid()); try { + wakeLock.acquire(); HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); if (connection instanceof HttpsURLConnection) { mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); @@ -253,6 +256,7 @@ public class HttpDownloadConnection implements Transferable { } finally { FileBackend.close(os); FileBackend.close(is); + wakeLock.release(); } } diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index 768915a9..69e9212e 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -3,6 +3,7 @@ package eu.siacs.conversations.http; import android.app.PendingIntent; import android.content.Intent; import android.net.Uri; +import android.os.PowerManager; import android.util.Log; import android.util.Pair; @@ -143,7 +144,9 @@ public class HttpUploadConnection implements Transferable { private void upload() { OutputStream os = null; HttpURLConnection connection = null; + PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_upload_"+message.getUuid()); try { + wakeLock.acquire(); Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString()); connection = (HttpURLConnection) mPutUrl.openConnection(); if (connection instanceof HttpsURLConnection) { @@ -211,6 +214,7 @@ public class HttpUploadConnection implements Transferable { if (connection != null) { connection.disconnect(); } + wakeLock.release(); } } } diff --git a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java index dbd7f376..1b49e365 100644 --- a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.services; +import android.content.Context; +import android.os.PowerManager; import android.util.Log; import android.util.Pair; @@ -118,4 +120,9 @@ public class AbstractConnectionManager { return null; } } + + public PowerManager.WakeLock createWakeLock(String name) { + PowerManager powerManager = (PowerManager) mXmppConnectionService.getSystemService(Context.POWER_SERVICE); + return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,name); + } } 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 d074bf9c..be1caa56 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -28,7 +28,6 @@ import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jid.Jid; @@ -1014,4 +1013,8 @@ public class JingleConnection implements Transferable { public int getProgress() { return this.mProgress; } + + public AbstractConnectionManager getConnectionManager() { + return this.mJingleConnectionManager; + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java index 7545dd64..556395ae 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.xmpp.jingle; +import android.os.PowerManager; import android.util.Log; import java.io.FileNotFoundException; @@ -96,14 +97,15 @@ public class JingleSocks5Transport extends JingleTransport { } - public void send(final DownloadableFile file, - final OnFileTransmissionStatusChanged callback) { + public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) { new Thread(new Runnable() { @Override public void run() { InputStream fileInputStream = null; + final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_"+connection.getSessionId()); try { + wakeLock.acquire(); MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.reset(); fileInputStream = connection.getFileInputStream(); @@ -138,6 +140,7 @@ public class JingleSocks5Transport extends JingleTransport { callback.onFileTransferAborted(); } finally { FileBackend.close(fileInputStream); + wakeLock.release(); } } }).start(); @@ -150,7 +153,9 @@ public class JingleSocks5Transport extends JingleTransport { @Override public void run() { OutputStream fileOutputStream = null; + final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_"+connection.getSessionId()); try { + wakeLock.acquire(); MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.reset(); inputStream.skip(45); @@ -166,7 +171,7 @@ public class JingleSocks5Transport extends JingleTransport { double size = file.getExpectedSize(); long remainingSize = file.getExpectedSize(); byte[] buffer = new byte[8192]; - int count = buffer.length; + int count; while (remainingSize > 0) { count = inputStream.read(buffer); if (count == -1) { @@ -194,7 +199,9 @@ public class JingleSocks5Transport extends JingleTransport { Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage()); callback.onFileTransferAborted(); } finally { + wakeLock.release(); FileBackend.close(fileOutputStream); + FileBackend.close(inputStream); } } }).start(); @@ -209,27 +216,9 @@ public class JingleSocks5Transport extends JingleTransport { } public void disconnect() { - if (this.outputStream != null) { - try { - this.outputStream.close(); - } catch (IOException e) { - - } - } - if (this.inputStream != null) { - try { - this.inputStream.close(); - } catch (IOException e) { - - } - } - if (this.socket != null) { - try { - this.socket.close(); - } catch (IOException e) { - - } - } + FileBackend.close(inputStream); + FileBackend.close(outputStream); + FileBackend.close(socket); } public boolean isEstablished() { -- cgit v1.2.3 From c0dcf4a55a50cb4c10699cb3bbf1b6765ce00633 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 11 Aug 2015 15:13:17 +0200 Subject: changed color of 'whispered' prefix fixes #1326 --- src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/main/java/eu') 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 aec11b76..d95f6b44 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -303,8 +303,7 @@ public class MessageAdapter extends ArrayAdapter { } final Spannable span = new SpannableString(privateMarker + " " + formattedBody); - span.setSpan(new ForegroundColorSpan(activity - .getSecondaryTextColor()), 0, privateMarker + span.setSpan(new ForegroundColorSpan(getMessageTextColor(type,false)), 0, privateMarker .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); span.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarker.length(), -- cgit v1.2.3 From 3677c6ec985f4b10d4d0c6a75c88973b2917ad4e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 11 Aug 2015 15:23:52 +0200 Subject: use same code that is used to open files for images fixes #1323 --- src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/main/java/eu') 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 d95f6b44..1ddd6c44 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -395,10 +395,7 @@ public class MessageAdapter extends ArrayAdapter { @Override public void onClick(View v) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(activity.xmppConnectionService - .getFileBackend().getJingleFileUri(message), "image/*"); - getContext().startActivity(intent); + openDownloadable(message); } }); viewHolder.image.setOnLongClickListener(openContextMenu); -- cgit v1.2.3 From dad90762b44878a436479a3998fec45111c9af46 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 11 Aug 2015 16:50:00 +0200 Subject: do not touch pictures that are already in the right format fixed #522 --- .../conversations/http/HttpUploadConnection.java | 9 +- .../conversations/persistance/FileBackend.java | 37 +++--- .../services/AbstractConnectionManager.java | 14 +- .../services/XmppConnectionService.java | 21 ++- .../eu/siacs/conversations/utils/FileUtils.java | 144 +++++++++++++++++++++ .../xmpp/jingle/JingleConnection.java | 44 ++++--- 6 files changed, 218 insertions(+), 51 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/utils/FileUtils.java (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index 69e9212e..2e545842 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -7,6 +7,7 @@ import android.os.PowerManager; import android.util.Log; import android.util.Pair; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -104,7 +105,13 @@ public class HttpUploadConnection implements Transferable { mXmppConnectionService.getRNG().nextBytes(this.key); this.file.setKeyAndIv(this.key); } - Pair pair = AbstractConnectionManager.createInputStream(file,true); + Pair pair; + try { + pair = AbstractConnectionManager.createInputStream(file, true); + } catch (FileNotFoundException e) { + fail(); + return; + } this.file.setExpectedSize(pair.second); this.mFileInputStream = pair.first; Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD); diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 13bbb276..931158e5 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -39,6 +39,7 @@ import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.ExifHelper; +import eu.siacs.conversations.utils.FileUtils; import eu.siacs.conversations.xmpp.pep.Avatar; public class FileBackend { @@ -126,25 +127,25 @@ public class FileBackend { return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true); } - public String getOriginalPath(Uri uri) { - String path = null; - if (uri.getScheme().equals("file")) { - return uri.getPath(); - } else if (uri.toString().startsWith("content://media/")) { - String[] projection = {MediaStore.MediaColumns.DATA}; - Cursor metaCursor = mXmppConnectionService.getContentResolver().query(uri, - projection, null, null, null); - if (metaCursor != null) { - try { - if (metaCursor.moveToFirst()) { - path = metaCursor.getString(0); - } - } finally { - metaCursor.close(); - } - } + public boolean useImageAsIs(Uri uri) { + String path = getOriginalPath(uri); + if (path == null) { + return false; } - return path; + Log.d(Config.LOGTAG,"using image as is. path: "+path); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + try { + BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(uri), null, options); + return (options.outWidth <= Config.IMAGE_SIZE && options.outHeight <= Config.IMAGE_SIZE && options.outMimeType.contains(Config.IMAGE_FORMAT.name().toLowerCase())); + } catch (FileNotFoundException e) { + return false; + } + } + + public String getOriginalPath(Uri uri) { + Log.d(Config.LOGTAG,"get original path for uri: "+uri.toString()); + return FileUtils.getPath(mXmppConnectionService,uri); } public DownloadableFile copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { diff --git a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java index 1b49e365..5def05dd 100644 --- a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java @@ -51,17 +51,13 @@ public class AbstractConnectionManager { } } - public static Pair createInputStream(DownloadableFile file, boolean gcm) { + public static Pair createInputStream(DownloadableFile file, boolean gcm) throws FileNotFoundException { FileInputStream is; int size; - try { - is = new FileInputStream(file); - size = (int) file.getSize(); - if (file.getKey() == null) { - return new Pair(is,size); - } - } catch (FileNotFoundException e) { - return null; + is = new FileInputStream(file); + size = (int) file.getSize(); + if (file.getKey() == null) { + return new Pair(is,size); } try { if (gcm) { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index ffe587d6..ed3bb879 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -404,8 +404,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - public void attachImageToConversation(final Conversation conversation, - final Uri uri, final UiCallback callback) { + public void attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) { + if (getFileBackend().useImageAsIs(uri)) { + Log.d(Config.LOGTAG,"using image as is"); + attachFileToConversation(conversation, uri, callback); + return; + } final Message message; if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); @@ -855,7 +859,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } else { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster"); } - iqPacket.query(Xmlns.ROSTER).setAttribute("ver",account.getRosterVersion()); + iqPacket.query(Xmlns.ROSTER).setAttribute("ver", account.getRosterVersion()); sendIqPacket(account,iqPacket,mIqParser); } @@ -1000,6 +1004,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void onMessageFound(Message message) { if (!getFileBackend().isFileAvailable(message)) { message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + final int s = message.getStatus(); + if(s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { + markMessage(message,Message.STATUS_SEND_FAILED); + } } } }); @@ -1011,7 +1019,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (message != null) { if (!getFileBackend().isFileAvailable(message)) { message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - updateConversationUi(); + final int s = message.getStatus(); + if(s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { + markMessage(message,Message.STATUS_SEND_FAILED); + } else { + updateConversationUi(); + } } return; } diff --git a/src/main/java/eu/siacs/conversations/utils/FileUtils.java b/src/main/java/eu/siacs/conversations/utils/FileUtils.java new file mode 100644 index 00000000..a13300d4 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/FileUtils.java @@ -0,0 +1,144 @@ +package eu.siacs.conversations.utils; + +import android.annotation.SuppressLint; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; + +public class FileUtils { + + /** + * Get a file path from a Uri. This will get the the path for Storage Access + * Framework Documents, as well as the _data field for the MediaStore and + * other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @author paulburke + */ + @SuppressLint("NewApi") + public static String getPath(final Context context, final Uri uri) { + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + + // TODO handle non-primary volumes + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{ + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + public static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, + null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } +} 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 be1caa56..7b140842 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -5,6 +5,7 @@ import android.net.Uri; import android.util.Log; import android.util.Pair; +import java.io.FileNotFoundException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; @@ -413,26 +414,31 @@ public class JingleConnection implements Transferable { content.setTransportId(this.transportId); this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false); Pair pair; - if (message.getEncryption() == Message.ENCRYPTION_OTR) { - Conversation conversation = this.message.getConversation(); - if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key"); - cancel(); + try { + if (message.getEncryption() == Message.ENCRYPTION_OTR) { + Conversation conversation = this.message.getConversation(); + if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not set symmetric key"); + cancel(); + } + this.file.setKeyAndIv(conversation.getSymmetricKey()); + pair = AbstractConnectionManager.createInputStream(this.file, false); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, true); + } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + this.file.setKey(mXmppAxolotlMessage.getInnerKey()); + this.file.setIv(mXmppAxolotlMessage.getIV()); + pair = AbstractConnectionManager.createInputStream(this.file, true); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, false).addChild(mXmppAxolotlMessage.toElement()); + } else { + pair = AbstractConnectionManager.createInputStream(this.file, false); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, false); } - this.file.setKeyAndIv(conversation.getSymmetricKey()); - pair = AbstractConnectionManager.createInputStream(this.file,false); - this.file.setExpectedSize(pair.second); - content.setFileOffer(this.file, true); - } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { - this.file.setKey(mXmppAxolotlMessage.getInnerKey()); - this.file.setIv(mXmppAxolotlMessage.getIV()); - pair = AbstractConnectionManager.createInputStream(this.file,true); - this.file.setExpectedSize(pair.second); - content.setFileOffer(this.file, false).addChild(mXmppAxolotlMessage.toElement()); - } else { - pair = AbstractConnectionManager.createInputStream(this.file,false); - this.file.setExpectedSize(pair.second); - content.setFileOffer(this.file, false); + } catch (FileNotFoundException e) { + cancel(); + return; } this.mFileInputStream = pair.first; this.transportId = this.mJingleConnectionManager.nextRandomId(); -- cgit v1.2.3 From 4539643f27ce38e7de121655effcae4070e25385 Mon Sep 17 00:00:00 2001 From: hlad Date: Fri, 14 Aug 2015 22:24:05 +0200 Subject: show HTTP upload availability on Edit account screen --- src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index b4ed044c..02b1d873 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -60,6 +60,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private TextView mServerInfoCSI; private TextView mServerInfoBlocking; private TextView mServerInfoPep; + private TextView mServerInfoHttpUpload; private TextView mSessionEst; private TextView mOtrFingerprint; private TextView mAxolotlFingerprint; @@ -347,6 +348,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mServerInfoBlocking = (TextView) findViewById(R.id.server_info_blocking); this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm); this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep); + this.mServerInfoHttpUpload = (TextView) findViewById(R.id.server_info_http_upload); 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); @@ -542,6 +544,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } else { this.mServerInfoPep.setText(R.string.server_info_unavailable); } + if (features.httpUpload()) { + this.mServerInfoHttpUpload.setText(R.string.server_info_available); + } else { + this.mServerInfoHttpUpload.setText(R.string.server_info_unavailable); + } final String otrFingerprint = this.mAccount.getOtrFingerprint(); if (otrFingerprint != null) { this.mOtrFingerprintBox.setVisibility(View.VISIBLE); -- cgit v1.2.3 From 32826ec29d28668b7f345ffcd45cd876dc980153 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 15 Aug 2015 14:14:33 +0200 Subject: provide extra interface to close sockets. fixes #1330 --- .../java/eu/siacs/conversations/persistance/FileBackend.java | 10 ++++++++++ .../eu/siacs/conversations/services/XmppConnectionService.java | 3 +-- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'src/main/java/eu') diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 931158e5..6e5a1ae3 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -22,6 +22,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.Socket; import java.net.URL; import java.security.DigestOutputStream; import java.security.MessageDigest; @@ -557,4 +558,13 @@ public class FileBackend { } } } + + public static void close(Socket socket) { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + } + } + } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index ed3bb879..a9a2f211 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -280,9 +280,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode()); } else if (account.getStatus() == Account.State.OFFLINE) { - resetSendingToWaiting(account); if (!account.isOptionSet(Account.OPTION_DISABLED)) { - int timeToReconnect = mRandom.nextInt(50) + 10; + int timeToReconnect = mRandom.nextInt(20) + 10; scheduleWakeUpCall(timeToReconnect,account.getUuid().hashCode()); } } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) { -- cgit v1.2.3