aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/main/java/eu/siacs/conversations/Config.java5
-rw-r--r--src/main/java/eu/siacs/conversations/OmemoActivity.java21
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java112
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java34
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java1
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java11
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Account.java3
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Contact.java2
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Conversation.java3
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java14
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java24
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java229
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java10
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java34
-rw-r--r--src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java8
-rw-r--r--src/main/java/eu/siacs/conversations/utils/TLSSocketFactory.java69
-rw-r--r--src/main/java/eu/siacs/conversations/utils/XmppUri.java2
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java161
-rw-r--r--src/main/res/layout/activity_edit_account.xml8
-rw-r--r--src/main/res/menu/editaccount.xml4
20 files changed, 462 insertions, 293 deletions
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java
index 33069439..a918811a 100644
--- a/src/main/java/eu/siacs/conversations/Config.java
+++ b/src/main/java/eu/siacs/conversations/Config.java
@@ -78,6 +78,10 @@ public final class Config {
public static final int MAX_DISPLAY_MESSAGE_CHARS = 4096;
+ public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
+
+ public static final long OMEMO_AUTO_EXPIRY = 7 * MILLISECONDS_IN_DAY;
+
public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb
public static final boolean DISABLE_HTTP_UPLOAD = false;
public static final boolean DISABLE_STRING_PREP = false; // setting to true might increase startup performance
@@ -97,7 +101,6 @@ public final class Config {
public static final boolean PARSE_REAL_JID_FROM_MUC_MAM = false; //dangerous if server doesn’t filter
- 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/OmemoActivity.java b/src/main/java/eu/siacs/conversations/OmemoActivity.java
index c4177cd2..5cda7e5f 100644
--- a/src/main/java/eu/siacs/conversations/OmemoActivity.java
+++ b/src/main/java/eu/siacs/conversations/OmemoActivity.java
@@ -15,6 +15,7 @@ import android.widget.Toast;
import java.security.cert.X509Certificate;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.widget.Switch;
@@ -60,9 +61,17 @@ public abstract class OmemoActivity extends XmppActivity {
}
}
- protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight) {
- final FingerprintStatus status = account.getAxolotlService().getFingerprintTrust(fingerprint);
- return status != null && addFingerprintRowWithListeners(keys, account, fingerprint, highlight, status, true, true, new CompoundButton.OnCheckedChangeListener() {
+ protected void addFingerprintRow(LinearLayout keys, final XmppAxolotlSession session, boolean highlight) {
+ final Account account = session.getAccount();
+ final String fingerprint = session.getFingerprint();
+ addFingerprintRowWithListeners(keys,
+ session.getAccount(),
+ session.getFingerprint(),
+ highlight,
+ session.getTrust(),
+ true,
+ true,
+ new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
account.getAxolotlService().setFingerprintTrust(fingerprint, FingerprintStatus.createActive(isChecked));
@@ -70,7 +79,7 @@ public abstract class OmemoActivity extends XmppActivity {
});
}
- protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
+ protected void addFingerprintRowWithListeners(LinearLayout keys, final Account account,
final String fingerprint,
boolean highlight,
FingerprintStatus status,
@@ -78,9 +87,6 @@ public abstract class OmemoActivity extends XmppActivity {
boolean undecidedNeedEnablement,
CompoundButton.OnCheckedChangeListener
onCheckedChangeListener) {
- if (status.isCompromised()) {
- 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);
@@ -184,7 +190,6 @@ public abstract class OmemoActivity extends XmppActivity {
key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
keys.addView(view);
- return true;
}
public void showPurgeKeyDialog(final Account account, final String fingerprint) {
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 702d4ada..377d26b9 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
@@ -25,13 +25,17 @@ import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
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 java.util.concurrent.atomic.AtomicBoolean;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
@@ -73,6 +77,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private int numPublishTriesOnEmptyPep = 0;
private boolean pepBroken = false;
+ private AtomicBoolean ownPushPending = new AtomicBoolean(false);
+
@Override
public void onAdvancedStreamFeaturesAvailable(Account account) {
if (Config.supportOmemo()
@@ -172,7 +178,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private void putDevicesForJid(String bareJid, List<Integer> 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());
IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
if(Config.X509_VERIFICATION) {
X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", ""));
@@ -297,16 +302,20 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return new AxolotlAddress(jid.toPreppedString(), 0);
}
- public Set<XmppAxolotlSession> findOwnSessions() {
+ public Collection<XmppAxolotlSession> findOwnSessions() {
AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid());
- return new HashSet<>(this.sessions.getAll(ownAddress).values());
+ ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(ownAddress).values());
+ Collections.sort(s);
+ return s;
}
- private Set<XmppAxolotlSession> findSessionsForContact(Contact contact) {
+ public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) {
AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
- return new HashSet<>(this.sessions.getAll(contactAddress).values());
+ ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(contactAddress).values());
+ Collections.sort(s);
+ return s;
}
private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
@@ -317,22 +326,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return sessions;
}
- public Set<String> getFingerprintsForOwnSessions() {
- Set<String> fingerprints = new HashSet<>();
- for (XmppAxolotlSession session : findOwnSessions()) {
- fingerprints.add(session.getFingerprint());
- }
- return fingerprints;
- }
-
- public Set<String> getFingerprintsForContact(final Contact contact) {
- Set<String> fingerprints = new HashSet<>();
- for (XmppAxolotlSession session : findSessionsForContact(contact)) {
- fingerprints.add(session.getFingerprint());
- }
- return fingerprints;
- }
-
private boolean hasAny(Jid jid) {
return sessions.hasAny(getAddressForJid(jid));
}
@@ -366,23 +359,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
- if (jid.toBareJid().equals(account.getJid().toBareJid())) {
- if (!deviceIds.isEmpty()) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attempts and pepBroken status.");
- pepBroken = false;
- numPublishTriesOnEmptyPep = 0;
- }
- if (deviceIds.contains(getOwnDeviceId())) {
- deviceIds.remove(getOwnDeviceId());
- } else {
- publishOwnDeviceId(deviceIds);
- }
- for (Integer deviceId : deviceIds) {
- AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId);
- if (sessions.get(ownDeviceAddress) == null) {
- buildSessionFromPEP(ownDeviceAddress);
- }
- }
+ boolean me = jid.toBareJid().equals(account.getJid().toBareJid());
+ if (me && ownPushPending.getAndSet(false)) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": ignoring own device update because of pending push");
+ return;
+ }
+ boolean needsPublishing = me && !deviceIds.contains(getOwnDeviceId());
+ if (me) {
+ deviceIds.remove(getOwnDeviceId());
}
Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toPreppedString()));
expiredDevices.removeAll(deviceIds);
@@ -401,10 +385,25 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
XmppAxolotlSession session = sessions.get(address);
if (session != null && session.getFingerprint() != null) {
if (!session.getTrust().isActive()) {
+ Log.d(Config.LOGTAG,"reactivating device with fingprint "+session.getFingerprint());
session.setTrust(session.getTrust().toActive());
}
}
}
+ if (me) {
+ if (Config.OMEMO_AUTO_EXPIRY != 0) {
+ needsPublishing |= deviceIds.removeAll(getExpiredDevices());
+ }
+ for (Integer deviceId : deviceIds) {
+ AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId);
+ if (sessions.get(ownDeviceAddress) == null) {
+ buildSessionFromPEP(ownDeviceAddress);
+ }
+ }
+ if (needsPublishing) {
+ publishOwnDeviceId(deviceIds);
+ }
+ }
this.deviceIds.put(jid, deviceIds);
mXmppConnectionService.keyStatusUpdated(null);
}
@@ -418,12 +417,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
deviceIds.add(getOwnDeviceId());
IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
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) {
- // TODO: implement this!
- }
- });
+ mXmppConnectionService.sendIqPacket(account, publish, null);
}
public void purgeKey(final String fingerprint) {
@@ -444,14 +438,30 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} else {
Element item = mXmppConnectionService.getIqParser().getItem(packet);
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
- if (!deviceIds.contains(getOwnDeviceId())) {
- publishOwnDeviceId(deviceIds);
- }
+ registerDevices(account.getJid().toBareJid(),deviceIds);
}
}
});
}
+ private Set<Integer> getExpiredDevices() {
+ Set<Integer> devices = new HashSet<>();
+ for(XmppAxolotlSession session : findOwnSessions()) {
+ if (session.getTrust().isActive()) {
+ long diff = System.currentTimeMillis() - session.getTrust().getLastActivation();
+ if (diff > Config.OMEMO_AUTO_EXPIRY) {
+ long lastMessageDiff = System.currentTimeMillis() - mXmppConnectionService.databaseBackend.getLastTimeFingerprintUsed(account,session.getFingerprint());
+ if (lastMessageDiff > Config.OMEMO_AUTO_EXPIRY) {
+ devices.add(session.getRemoteAddress().getDeviceId());
+ session.setTrust(session.getTrust().toInactive());
+ Log.d(Config.LOGTAG, "added own device " + session.getFingerprint() + " to list of expired devices. Last message received "+(lastMessageDiff/1000)+"s ago");
+ }
+ }
+ }
+ }
+ return devices;
+ }
+
public void publishOwnDeviceId(Set<Integer> deviceIds) {
Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
if (!deviceIdsCopy.contains(getOwnDeviceId())) {
@@ -470,9 +480,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
deviceIdsCopy.add(getOwnDeviceId());
IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy);
+ ownPushPending.set(true);
mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
+ ownPushPending.set(false);
if (packet.getType() == IqPacket.TYPE.ERROR) {
pepBroken = true;
Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
@@ -954,18 +966,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
account.getJid().toBareJid(), getOwnDeviceId());
Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
- Set<XmppAxolotlSession> ownSessions = findOwnSessions();
+ Collection<XmppAxolotlSession> ownSessions = findOwnSessions();
if (remoteSessions.isEmpty()) {
return null;
}
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements...");
for (XmppAxolotlSession session : remoteSessions) {
- Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
axolotlMessage.addDevice(session);
}
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements...");
for (XmppAxolotlSession session : ownSessions) {
- Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
axolotlMessage.addDevice(session);
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java
index b594b5de..cfd3b214 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java
@@ -3,10 +3,13 @@ package eu.siacs.conversations.crypto.axolotl;
import android.content.ContentValues;
import android.database.Cursor;
-public class FingerprintStatus {
+public class FingerprintStatus implements Comparable<FingerprintStatus> {
+
+ private static final long DO_NOT_OVERWRITE = -1;
private Trust trust = Trust.UNTRUSTED;
private boolean active = false;
+ private long lastActivation = DO_NOT_OVERWRITE;
@Override
public boolean equals(Object o) {
@@ -34,6 +37,9 @@ public class FingerprintStatus {
final ContentValues contentValues = new ContentValues();
contentValues.put(SQLiteAxolotlStore.TRUST,trust.toString());
contentValues.put(SQLiteAxolotlStore.ACTIVE,active ? 1 : 0);
+ if (lastActivation != DO_NOT_OVERWRITE) {
+ contentValues.put(SQLiteAxolotlStore.LAST_ACTIVATION,lastActivation);
+ }
return contentValues;
}
@@ -45,6 +51,7 @@ public class FingerprintStatus {
status.trust = Trust.UNTRUSTED;
}
status.active = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.ACTIVE)) > 0;
+ status.lastActivation = cursor.getLong(cursor.getColumnIndex(SQLiteAxolotlStore.LAST_ACTIVATION));
return status;
}
@@ -52,6 +59,7 @@ public class FingerprintStatus {
final FingerprintStatus status = new FingerprintStatus();
status.trust = Trust.UNDECIDED;
status.active = true;
+ status.lastActivation = System.currentTimeMillis();
return status;
}
@@ -92,6 +100,9 @@ public class FingerprintStatus {
public FingerprintStatus toActive() {
FingerprintStatus status = new FingerprintStatus();
status.trust = trust;
+ if (!status.active) {
+ status.lastActivation = System.currentTimeMillis();
+ }
status.active = true;
return status;
}
@@ -128,6 +139,27 @@ public class FingerprintStatus {
return status;
}
+ @Override
+ public int compareTo(FingerprintStatus o) {
+ if (active == o.active) {
+ if (lastActivation > o.lastActivation) {
+ return -1;
+ } else if (lastActivation < o.lastActivation) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else if (active){
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ public long getLastActivation() {
+ return lastActivation;
+ }
+
public enum Trust {
COMPROMISED,
UNDECIDED,
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 a3647be7..cd605248 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
@@ -38,6 +38,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
public static final String TRUSTED = "trusted"; //no longer used
public static final String TRUST = "trust";
public static final String ACTIVE = "active";
+ public static final String LAST_ACTIVATION = "last_activation";
public static final String OWN = "ownkey";
public static final String CERTIFICATE = "certificate";
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 572d62c8..725757a3 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
@@ -22,7 +22,7 @@ import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
-public class XmppAxolotlSession {
+public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> {
private final SessionCipher cipher;
private final SQLiteAxolotlStore sqLiteAxolotlStore;
private final AxolotlAddress remoteAddress;
@@ -132,4 +132,13 @@ public class XmppAxolotlSession {
return null;
}
}
+
+ public Account getAccount() {
+ return account;
+ }
+
+ @Override
+ public int compareTo(XmppAxolotlSession o) {
+ return getTrust().compareTo(o.getTrust());
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java
index 77828b03..e0bb6d1d 100644
--- a/src/main/java/eu/siacs/conversations/entities/Account.java
+++ b/src/main/java/eu/siacs/conversations/entities/Account.java
@@ -19,6 +19,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
@@ -489,7 +490,7 @@ public class Account extends AbstractEntity {
if (publicKey == null || !(publicKey instanceof DSAPublicKey)) {
return null;
}
- this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey);
+ this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey).toLowerCase(Locale.US);
return this.otrFingerprint;
} catch (final OtrCryptoException ignored) {
return null;
diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java
index 55f5443a..72ce1a5a 100644
--- a/src/main/java/eu/siacs/conversations/entities/Contact.java
+++ b/src/main/java/eu/siacs/conversations/entities/Contact.java
@@ -301,7 +301,7 @@ public class Contact implements ListItem, Blockable {
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));
+ fingerprints.add(prints.getString(i).toLowerCase(Locale.US));
}
}
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java
index 245ff288..9605fefc 100644
--- a/src/main/java/eu/siacs/conversations/entities/Conversation.java
+++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java
@@ -20,6 +20,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.PgpDecryptionService;
@@ -627,7 +628,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return null;
}
DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession().getRemotePublicKey();
- this.otrFingerprint = getAccount().getOtrService().getFingerprint(remotePubKey);
+ this.otrFingerprint = getAccount().getOtrService().getFingerprint(remotePubKey).toLowerCase(Locale.US);
} catch (final OtrCryptoException | UnsupportedOperationException ignored) {
return null;
}
diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
index 1a417f32..18c60bff 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
@@ -24,6 +24,7 @@ import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.SSLSocketHelper;
+import eu.siacs.conversations.utils.TLSSocketFactory;
public class HttpConnectionManager extends AbstractConnectionManager {
@@ -77,18 +78,7 @@ public class HttpConnectionManager extends AbstractConnectionManager {
new StrictHostnameVerifier());
}
try {
- final SSLContext sc = SSLSocketHelper.getSSLContext();
- sc.init(null, new X509TrustManager[]{trustManager},
- mXmppConnectionService.getRNG());
-
- final SSLSocketFactory sf = sc.getSocketFactory();
- final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
- sf.getSupportedCipherSuites());
- if (cipherSuites.length > 0) {
- sc.getDefaultSSLParameters().setCipherSuites(cipherSuites);
-
- }
-
+ final SSLSocketFactory sf = new TLSSocketFactory(new X509TrustManager[]{trustManager}, mXmppConnectionService.getRNG());
connection.setSSLSocketFactory(sf);
connection.setHostnameVerifier(hostnameVerifier);
} catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
index 1e50ce9c..c111efa4 100644
--- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -55,7 +55,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private static DatabaseBackend instance = null;
private static final String DATABASE_NAME = "history";
- private static final int DATABASE_VERSION = 31;
+ private static final int DATABASE_VERSION = 32;
private static String CREATE_CONTATCS_STATEMENT = "create table "
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@@ -132,6 +132,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ SQLiteAxolotlStore.CERTIFICATE + " BLOB, "
+ SQLiteAxolotlStore.TRUST + " TEXT, "
+ SQLiteAxolotlStore.ACTIVE + " NUMBER, "
+ + SQLiteAxolotlStore.LAST_ACTIVATION + " NUMBER,"
+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ SQLiteAxolotlStore.ACCOUNT
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
@@ -377,6 +378,12 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
}
+ if (oldVersion < 32 && newVersion >= 32) {
+ db.execSQL("ALTER TABLE "+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN "+SQLiteAxolotlStore.LAST_ACTIVATION + " NUMBER");
+ ContentValues defaults = new ContentValues();
+ defaults.put(SQLiteAxolotlStore.LAST_ACTIVATION,System.currentTimeMillis());
+ db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME,defaults,null,null);
+ }
}
private static ContentValues createFingerprintStatusContentValues(FingerprintStatus.Trust trust, boolean active) {
@@ -780,6 +787,20 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
}
+ public long getLastTimeFingerprintUsed(Account account, String fingerprint) {
+ String SQL = "select messages.timeSent from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and messages.axolotl_fingerprint=? order by messages.timesent desc limit 1";
+ String[] args = {account.getUuid(), fingerprint};
+ Cursor cursor = getReadableDatabase().rawQuery(SQL,args);
+ long time;
+ if (cursor.moveToFirst()) {
+ time = cursor.getLong(0);
+ } else {
+ time = 0;
+ }
+ cursor.close();
+ return time;
+ }
+
public Pair<Long,String> getLastClearDate(Account account) {
SQLiteDatabase db = this.getReadableDatabase();
String[] columns = {Conversation.ATTRIBUTES};
@@ -1046,6 +1067,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) {
String[] columns = {SQLiteAxolotlStore.TRUST,
SQLiteAxolotlStore.ACTIVE,
+ SQLiteAxolotlStore.LAST_ACTIVATION,
SQLiteAxolotlStore.KEY};
ArrayList<String> selectionArgs = new ArrayList<>(4);
selectionArgs.add(account.getUuid());
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index 1321485d..8c691102 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -67,7 +67,6 @@ import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
-import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Blockable;
import eu.siacs.conversations.entities.Bookmark;
@@ -335,32 +334,34 @@ public class XmppConnectionService extends Service {
}
account.pendingConferenceJoins.clear();
scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode());
- } else if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED) {
- resetSendingToWaiting(account);
- if (!account.isOptionSet(Account.OPTION_DISABLED)) {
- synchronized (mLowPingTimeoutMode) {
- if (mLowPingTimeoutMode.contains(account.getJid().toBareJid())) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": went into offline state during low ping mode. reconnecting now");
- reconnectAccount(account, true, false);
- } else {
- int timeToReconnect = mRandom.nextInt(20) + 10;
- scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
+ } else {
+ if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED) {
+ resetSendingToWaiting(account);
+ if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ synchronized (mLowPingTimeoutMode) {
+ if (mLowPingTimeoutMode.contains(account.getJid().toBareJid())) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": went into offline state during low ping mode. reconnecting now");
+ reconnectAccount(account, true, false);
+ } else {
+ int timeToReconnect = mRandom.nextInt(10) + 2;
+ scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
+ }
}
}
- }
- } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
- databaseBackend.updateAccount(account);
- reconnectAccount(account, true, false);
- } else if ((account.getStatus() != Account.State.CONNECTING)
- && (account.getStatus() != Account.State.NO_INTERNET)) {
- resetSendingToWaiting(account);
- if (connection != null) {
- int next = connection.getTimeToNextAttempt();
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
- + ": error connecting account. try again in "
- + next + "s for the "
- + (connection.getAttempt() + 1) + " time");
- scheduleWakeUpCall(next, account.getUuid().hashCode());
+ } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
+ databaseBackend.updateAccount(account);
+ reconnectAccount(account, true, false);
+ } else if ((account.getStatus() != Account.State.CONNECTING)
+ && (account.getStatus() != Account.State.NO_INTERNET)) {
+ resetSendingToWaiting(account);
+ if (connection != null) {
+ int next = connection.getTimeToNextAttempt();
+ Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ + ": error connecting account. try again in "
+ + next + "s for the "
+ + (connection.getAttempt() + 1) + " time");
+ scheduleWakeUpCall(next, account.getUuid().hashCode());
+ }
}
}
getNotificationService().updateErrorNotification();
@@ -547,7 +548,7 @@ public class XmppConnectionService extends Service {
switch (action) {
case ConnectivityManager.CONNECTIVITY_ACTION:
if (hasInternetConnection() && Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) {
- resetAllAttemptCounts(true);
+ resetAllAttemptCounts(true, false);
}
break;
case ACTION_MERGE_PHONE_CONTACTS:
@@ -573,7 +574,7 @@ public class XmppConnectionService extends Service {
dismissErrorNotifications();
break;
case ACTION_TRY_AGAIN:
- resetAllAttemptCounts(false);
+ resetAllAttemptCounts(false, true);
interactive = true;
break;
case ACTION_REPLY_TO_CONVERSATION:
@@ -611,103 +612,108 @@ public class XmppConnectionService extends Service {
break;
}
}
- this.wakeLock.acquire();
+ synchronized (this) {
+ this.wakeLock.acquire();
+ boolean pingNow = ConnectivityManager.CONNECTIVITY_ACTION.equals(action);
+ HashSet<Account> pingCandidates = new HashSet<>();
+ for (Account account : accounts) {
+ pingNow |= processAccountState(account,
+ interactive,
+ "ui".equals(action),
+ CryptoHelper.getAccountFingerprint(account).equals(pushedAccountHash),
+ pingCandidates);
+ }
+ if (pingNow) {
+ for (Account account : pingCandidates) {
+ final boolean lowTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid());
+ account.getXmppConnection().sendPing();
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping (action=" + action + ",lowTimeout=" + Boolean.toString(lowTimeout) + ")");
+ scheduleWakeUpCall(lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT, account.getUuid().hashCode());
+ }
+ }
+ if (wakeLock.isHeld()) {
+ try {
+ wakeLock.release();
+ } catch (final RuntimeException ignored) {
+ }
+ }
+ }
+ return START_STICKY;
+ }
+ private boolean processAccountState(Account account, boolean interactive, boolean isUiAction, boolean isAccountPushed, HashSet<Account> pingCandidates) {
boolean pingNow = false;
- HashSet<Account> pingCandidates = new HashSet<>();
-
- for (Account account : accounts) {
- if (!account.isOptionSet(Account.OPTION_DISABLED)) {
- if (!hasInternetConnection()) {
- account.setStatus(Account.State.NO_INTERNET);
+ if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ if (!hasInternetConnection()) {
+ account.setStatus(Account.State.NO_INTERNET);
+ if (statusListener != null) {
+ statusListener.onStatusChanged(account);
+ }
+ } else {
+ if (account.getStatus() == Account.State.NO_INTERNET) {
+ account.setStatus(Account.State.OFFLINE);
if (statusListener != null) {
statusListener.onStatusChanged(account);
}
- } else {
- if (account.getStatus() == Account.State.NO_INTERNET) {
- account.setStatus(Account.State.OFFLINE);
- if (statusListener != null) {
- statusListener.onStatusChanged(account);
- }
- }
- if (account.getStatus() == Account.State.ONLINE) {
- synchronized (mLowPingTimeoutMode) {
- long lastReceived = account.getXmppConnection().getLastPacketReceived();
- long lastSent = account.getXmppConnection().getLastPingSent();
- long pingInterval = "ui".equals(action) ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000;
- long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime();
- int pingTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid()) ? Config.LOW_PING_TIMEOUT * 1000 : Config.PING_TIMEOUT * 1000;
- long pingTimeoutIn = (lastSent + pingTimeout) - SystemClock.elapsedRealtime();
- if (lastSent > lastReceived) {
- if (pingTimeoutIn < 0) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout");
- this.reconnectAccount(account, true, interactive);
- } else {
- int secs = (int) (pingTimeoutIn / 1000);
- this.scheduleWakeUpCall(secs, account.getUuid().hashCode());
+ }
+ if (account.getStatus() == Account.State.ONLINE) {
+ synchronized (mLowPingTimeoutMode) {
+ long lastReceived = account.getXmppConnection().getLastPacketReceived();
+ long lastSent = account.getXmppConnection().getLastPingSent();
+ long pingInterval = isUiAction ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000;
+ long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime();
+ int pingTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid()) ? Config.LOW_PING_TIMEOUT * 1000 : Config.PING_TIMEOUT * 1000;
+ long pingTimeoutIn = (lastSent + pingTimeout) - SystemClock.elapsedRealtime();
+ if (lastSent > lastReceived) {
+ if (pingTimeoutIn < 0) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout");
+ this.reconnectAccount(account, true, interactive);
+ } else {
+ int secs = (int) (pingTimeoutIn / 1000);
+ this.scheduleWakeUpCall(secs, account.getUuid().hashCode());
+ }
+ } else {
+ pingCandidates.add(account);
+ if (isAccountPushed) {
+ pingNow = true;
+ if (mLowPingTimeoutMode.add(account.getJid().toBareJid())) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": entering low ping timeout mode");
}
+ } else if (msToNextPing <= 0) {
+ pingNow = true;
} else {
- pingCandidates.add(account);
- if (CryptoHelper.getAccountFingerprint(account).equals(pushedAccountHash)) {
- pingNow = true;
- if (mLowPingTimeoutMode.add(account.getJid().toBareJid())) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": entering low ping timeout mode");
- }
- } else if (msToNextPing <= 0) {
- pingNow = true;
- } else {
- this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode());
- if (mLowPingTimeoutMode.remove(account.getJid().toBareJid())) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": leaving low ping timeout mode");
- }
+ this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode());
+ if (mLowPingTimeoutMode.remove(account.getJid().toBareJid())) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": leaving low ping timeout mode");
}
}
}
- } else if (account.getStatus() == Account.State.OFFLINE) {
+ }
+ } else if (account.getStatus() == Account.State.OFFLINE) {
+ reconnectAccount(account, true, interactive);
+ } else if (account.getStatus() == Account.State.CONNECTING) {
+ long secondsSinceLastConnect = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000;
+ long secondsSinceLastDisco = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastDiscoStarted()) / 1000;
+ long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco;
+ long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect;
+ if (timeout < 0) {
+ Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting (secondsSinceLast="+secondsSinceLastConnect+")");
+ account.getXmppConnection().resetAttemptCount(false);
reconnectAccount(account, true, interactive);
- } else if (account.getStatus() == Account.State.CONNECTING) {
- long secondsSinceLastConnect = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000;
- long secondsSinceLastDisco = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastDiscoStarted()) / 1000;
- long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco;
- long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect;
- if (timeout < 0) {
- Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting");
- account.getXmppConnection().resetAttemptCount();
- reconnectAccount(account, true, interactive);
- } else if (discoTimeout < 0) {
- account.getXmppConnection().sendDiscoTimeout();
- scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode());
- } else {
- scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode());
- }
+ } else if (discoTimeout < 0) {
+ account.getXmppConnection().sendDiscoTimeout();
+ scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode());
} else {
- if (account.getXmppConnection().getTimeToNextAttempt() <= 0) {
- reconnectAccount(account, true, interactive);
- }
+ scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode());
+ }
+ } else {
+ if (account.getXmppConnection().getTimeToNextAttempt() <= 0) {
+ reconnectAccount(account, true, interactive);
}
}
- if (mOnAccountUpdate != null) {
- mOnAccountUpdate.onAccountUpdate();
- }
- }
- }
- if (pingNow) {
- for (Account account : pingCandidates) {
- synchronized (mLowPingTimeoutMode) {
- final boolean lowTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid());
- account.getXmppConnection().sendPing();
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping (action=" + action + ",lowTimeout=" + Boolean.toString(lowTimeout) + ")");
- scheduleWakeUpCall(lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT, account.getUuid().hashCode());
- }
- }
- }
- if (wakeLock.isHeld()) {
- try {
- wakeLock.release();
- } catch (final RuntimeException ignored) {
}
}
- return START_STICKY;
+ return pingNow;
}
public boolean isDataSaverDisabled() {
@@ -814,13 +820,13 @@ public class XmppConnectionService extends Service {
}
}
- private void resetAllAttemptCounts(boolean reallyAll) {
+ private void resetAllAttemptCounts(boolean reallyAll, boolean retryImmediately) {
Log.d(Config.LOGTAG, "resetting all attempt counts");
for (Account account : accounts) {
if (account.hasErrorStatus() || reallyAll) {
final XmppConnection connection = account.getXmppConnection();
if (connection != null) {
- connection.resetAttemptCount();
+ connection.resetAttemptCount(retryImmediately);
}
}
if (account.setShowErrorNotification(true)) {
@@ -2890,8 +2896,6 @@ public class XmppConnectionService extends Service {
if (connection == null) {
connection = createConnection(account);
account.setXmppConnection(connection);
- } else {
- connection.interrupt();
}
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
if (!force) {
@@ -2900,6 +2904,7 @@ public class XmppConnectionService extends Service {
Thread thread = new Thread(connection);
connection.setInteractive(interactive);
connection.prepareNewConnection();
+ connection.interrupt();
thread.start();
scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode());
} else {
diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
index a567f151..b8313d9e 100644
--- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -41,6 +41,7 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem;
@@ -445,9 +446,12 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
}
}
if (Config.supportOmemo()) {
- for (final String fingerprint : contact.getAccount().getAxolotlService().getFingerprintsForContact(contact)) {
- boolean highlight = fingerprint.equals(messageFingerprint);
- hasKeys |= addFingerprintRow(keys, contact.getAccount(), fingerprint, highlight);
+ for (final XmppAxolotlSession session : contact.getAccount().getAxolotlService().findSessionsForContact(contact)) {
+ if (!session.getTrust().isCompromised()) {
+ boolean highlight = session.getFingerprint().equals(messageFingerprint);
+ hasKeys = true;
+ addFingerprintRow(keys, session, highlight);
+ }
}
}
if (Config.supportOpenPgp() && contact.getPgpKeyId() != 0) {
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index 0f38173b..693b7185 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -44,6 +44,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.OmemoActivity;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested;
import eu.siacs.conversations.services.XmppConnectionService;
@@ -251,6 +252,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
private TableRow mPushRow;
private String mSavedInstanceAccount;
private boolean mSavedInstanceInit = false;
+ private Button mClearDevicesButton;
public void refreshUiReal() {
invalidateOptionsMenu();
@@ -502,6 +504,13 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
this.mNamePort = (LinearLayout) findViewById(R.id.name_port);
this.mHostname = (EditText) findViewById(R.id.hostname);
this.mHostname.addTextChangedListener(mTextWatcher);
+ this.mClearDevicesButton = (Button) findViewById(R.id.clear_devices);
+ this.mClearDevicesButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showWipePepDialog();
+ }
+ });
this.mPort = (EditText) findViewById(R.id.port);
this.mPort.setText("5222");
this.mPort.addTextChangedListener(mTextWatcher);
@@ -540,7 +549,6 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
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 showPassword = menu.findItem(R.id.action_show_password);
- final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices);
final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate);
final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs);
final MenuItem changePresence = menu.findItem(R.id.action_change_presence);
@@ -554,17 +562,12 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
changePassword.setVisible(false);
}
mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam());
- Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
- if (otherDevices == null || otherDevices.isEmpty() || !Config.supportOmemo()) {
- clearDevices.setVisible(false);
- }
changePresence.setVisible(manuallyChangePresence());
} else {
showQrCode.setVisible(false);
showBlocklist.setVisible(false);
showMoreInfo.setVisible(false);
changePassword.setVisible(false);
- clearDevices.setVisible(false);
mamPrefs.setVisible(false);
changePresence.setVisible(false);
}
@@ -706,9 +709,6 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
case R.id.action_mam_prefs:
editMamPrefs();
break;
- case R.id.action_clear_devices:
- showWipePepDialog();
- break;
case R.id.action_renew_certificate:
renewCertificate();
break;
@@ -904,15 +904,21 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
boolean hasKeys = false;
keys.removeAllViews();
- for (final String fingerprint : mAccount.getAxolotlService().getFingerprintsForOwnSessions()) {
- if (ownAxolotlFingerprint.equals(fingerprint)) {
- continue;
+ for(XmppAxolotlSession session : mAccount.getAxolotlService().findOwnSessions()) {
+ if (!session.getTrust().isCompromised()) {
+ boolean highlight = session.getFingerprint().equals(messageFingerprint);
+ addFingerprintRow(keys,session,highlight);
+ hasKeys = true;
}
- boolean highlight = fingerprint.equals(messageFingerprint);
- hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight);
}
if (hasKeys && Config.supportOmemo()) {
keysCard.setVisibility(View.VISIBLE);
+ Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
+ if (otherDevices == null || otherDevices.isEmpty()) {
+ mClearDevicesButton.setVisibility(View.GONE);
+ } else {
+ mClearDevicesButton.setVisibility(View.VISIBLE);
+ }
} else {
keysCard.setVisibility(View.GONE);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
index 2d24fbbf..a55863fd 100644
--- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
@@ -401,7 +401,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
EnterJidDialog dialog = new EnterJidDialog(
this, mKnownHosts, mActivatedAccounts,
getString(R.string.create_contact), getString(R.string.create),
- prefilledJid, null, !invite.hasFingerprints()
+ prefilledJid, null, invite == null || !invite.hasFingerprints()
);
dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() {
@@ -420,8 +420,10 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
if (contact.showInRoster()) {
throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists));
} else {
- //contact.addOtrFingerprint(fingerprint);
xmppConnectionService.createContact(contact);
+ if (invite != null && invite.hasFingerprints()) {
+ xmppConnectionService.verifyFingerprints(contact,invite.getFingerprints());
+ }
switchToConversation(contact);
return true;
}
@@ -843,7 +845,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
private boolean handleJid(Invite invite) {
Account account = xmppConnectionService.findAccountByJid(invite.getJid());
- if (account != null && invite.hasFingerprints()) {
+ if (account != null && !account.isOptionSet(Account.OPTION_DISABLED) && invite.hasFingerprints()) {
if (xmppConnectionService.verifyFingerprints(account,invite.getFingerprints())) {
switchToAccount(account);
finish();
diff --git a/src/main/java/eu/siacs/conversations/utils/TLSSocketFactory.java b/src/main/java/eu/siacs/conversations/utils/TLSSocketFactory.java
new file mode 100644
index 00000000..9d8a4111
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/utils/TLSSocketFactory.java
@@ -0,0 +1,69 @@
+package eu.siacs.conversations.utils;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
+
+public class TLSSocketFactory extends SSLSocketFactory {
+
+ private final SSLSocketFactory internalSSLSocketFactory;
+
+ public TLSSocketFactory(X509TrustManager[] trustManager, SecureRandom random) throws KeyManagementException, NoSuchAlgorithmException {
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, trustManager, random);
+ this.internalSSLSocketFactory = context.getSocketFactory();
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return CryptoHelper.getOrderedCipherSuites(internalSSLSocketFactory.getDefaultCipherSuites());
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return internalSSLSocketFactory.getSupportedCipherSuites();
+ }
+
+ @Override
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
+ }
+
+ private static Socket enableTLSOnSocket(Socket socket) {
+ if(socket != null && (socket instanceof SSLSocket)) {
+ try {
+ SSLSocketHelper.setSecurity((SSLSocket) socket);
+ } catch (NoSuchAlgorithmException e) {
+ //ignoring
+ }
+ }
+ return socket;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java
index 1fc9812a..8cf5eeac 100644
--- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java
+++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java
@@ -85,7 +85,7 @@ public class XmppUri {
String[] parts = pair.split("=",2);
if (parts.length == 2) {
String key = parts[0].toLowerCase(Locale.US);
- String value = parts[1];
+ String value = parts[1].toLowerCase(Locale.US);
if (OTR_URI_PARAM.equals(key)) {
fingerprints.add(new Fingerprint(FingerprintType.OTR,value));
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index d6a3b2cb..128e1187 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -218,7 +218,10 @@ public class XmppConnection implements Runnable {
mXmppConnectionService = service;
}
- protected void changeStatus(final Account.State nextStatus) {
+ protected synchronized void changeStatus(final Account.State nextStatus) {
+ if (Thread.currentThread().isInterrupted()) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": not changing status to "+nextStatus+" because thread was interrupted");
+ }
if (account.getStatus() != nextStatus) {
if ((nextStatus == Account.State.OFFLINE)
&& (account.getStatus() != Account.State.CONNECTING)
@@ -262,6 +265,7 @@ public class XmppConnection implements Runnable {
break;
}
try {
+ Socket localSocket;
shouldAuthenticate = needsBinding = !account.isOptionSet(Account.OPTION_REGISTER);
tagReader = new XmlReader(wakeLock);
tagWriter = new TagWriter();
@@ -276,8 +280,15 @@ public class XmppConnection implements Runnable {
destination = account.getHostname();
}
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via Tor");
- socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort());
- startXmpp();
+ localSocket = SocksSocketFactory.createSocketOverTor(destination, account.getPort());
+ try {
+ startXmpp(localSocket);
+ } catch (InterruptedException e) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream");
+ return;
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
+ }
} else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) {
InetSocketAddress address = new InetSocketAddress(account.getHostname(), account.getPort());
@@ -288,33 +299,47 @@ public class XmppConnection implements Runnable {
if (features.encryptionEnabled) {
try {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
- socket = tlsFactoryVerifier.factory.createSocket();
- socket.connect(address, Config.SOCKET_TIMEOUT * 1000);
- final SSLSession session = ((SSLSocket) socket).getSession();
+ localSocket = tlsFactoryVerifier.factory.createSocket();
+ localSocket.connect(address, Config.SOCKET_TIMEOUT * 1000);
+ final SSLSession session = ((SSLSocket) localSocket).getSession();
if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), session)) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
throw new SecurityException();
}
} catch (KeyManagementException e) {
features.encryptionEnabled = false;
- socket = new Socket();
+ localSocket = new Socket();
}
} else {
- socket = new Socket();
- socket.connect(address, Config.SOCKET_TIMEOUT * 1000);
+ localSocket = new Socket();
+ localSocket.connect(address, Config.SOCKET_TIMEOUT * 1000);
}
} catch (IOException e) {
throw new UnknownHostException();
}
- startXmpp();
+ try {
+ startXmpp(localSocket);
+ } catch (InterruptedException e) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream");
+ return;
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
+ }
} else if (DNSHelper.isIp(account.getServer().toString())) {
- socket = new Socket();
+ localSocket = new Socket();
try {
- socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
+ localSocket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
} catch (IOException e) {
throw new UnknownHostException();
}
- startXmpp();
+ try {
+ startXmpp(localSocket);
+ } catch (InterruptedException e) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream");
+ return;
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
+ }
} else {
final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService);
final ArrayList<Parcelable> values = result.getParcelableArrayList("values");
@@ -350,32 +375,37 @@ public class XmppConnection implements Runnable {
}
if (!features.encryptionEnabled) {
- socket = new Socket();
- socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
+ localSocket = new Socket();
+ localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
} else {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
- socket = tlsFactoryVerifier.factory.createSocket();
+ localSocket = tlsFactoryVerifier.factory.createSocket();
- if (socket == null) {
+ if (localSocket == null) {
throw new IOException("could not initialize ssl socket");
}
- SSLSocketHelper.setSecurity((SSLSocket) socket);
- SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart());
- SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client");
+ SSLSocketHelper.setSecurity((SSLSocket) localSocket);
+ SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) localSocket, account.getServer().getDomainpart());
+ SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) localSocket, "xmpp-client");
- socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
+ localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
- if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) {
+ if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) localSocket).getSession())) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
throw new SecurityException();
}
}
-
- if (startXmpp())
+ if (startXmpp(localSocket)) {
break; // successfully connected to server that speaks xmpp
+ } else {
+ localSocket.close();
+ }
} catch (final SecurityException e) {
throw e;
+ } catch (InterruptedException e) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream");
+ return;
} catch (final Throwable e) {
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() + "(" + e.getClass().getName() + ")");
if (!iterator.hasNext()) {
@@ -385,8 +415,10 @@ public class XmppConnection implements Runnable {
}
}
processStream();
- } catch (final java.lang.SecurityException e) {
+ } catch (final java.lang.SecurityException e) {
this.changeStatus(Account.State.MISSING_INTERNET_PERMISSION);
+ } catch (final RegistrationNotSupportedException e) {
+ this.changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED);
} catch (final IncompatibleServerException e) {
this.changeStatus(Account.State.INCOMPATIBLE_SERVER);
} catch (final SecurityException e) {
@@ -410,12 +442,16 @@ public class XmppConnection implements Runnable {
this.changeStatus(Account.State.OFFLINE);
this.attempt = Math.max(0, this.attempt - 1);
} finally {
- forceCloseSocket();
- if (wakeLock.isHeld()) {
- try {
- wakeLock.release();
- } catch (final RuntimeException ignored) {
+ if (!Thread.currentThread().isInterrupted()) {
+ forceCloseSocket();
+ if (wakeLock.isHeld()) {
+ try {
+ wakeLock.release();
+ } catch (final RuntimeException ignored) {
+ }
}
+ } else {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": not force closing socket and releasing wake lock because thread was interrupted");
}
}
}
@@ -423,27 +459,18 @@ public class XmppConnection implements Runnable {
/**
* Starts xmpp protocol, call after connecting to socket
* @return true if server returns with valid xmpp, false otherwise
- * @throws IOException Unknown tag on connect
- * @throws XmlPullParserException Bad Xml
- * @throws NoSuchAlgorithmException Other error
*/
- private boolean startXmpp() throws IOException, XmlPullParserException, NoSuchAlgorithmException {
+ private boolean startXmpp(Socket socket) throws Exception {
+ if (Thread.currentThread().isInterrupted()) {
+ throw new InterruptedException();
+ }
+ this.socket = socket;
tagWriter.setOutputStream(socket.getOutputStream());
tagReader.setInputStream(socket.getInputStream());
tagWriter.beginDocument();
sendStartStream();
- Tag nextTag;
- while ((nextTag = tagReader.readTag()) != null) {
- if (nextTag.isStart("stream")) {
- return true;
- } else {
- throw new IOException("unknown tag on connect");
- }
- }
- if (socket.isConnected()) {
- socket.close();
- }
- return false;
+ final Tag tag = tagReader.readTag();
+ return tag != null && tag.isStart("stream");
}
private static class TlsFactoryVerifier {
@@ -812,10 +839,8 @@ public class XmppConnection implements Runnable {
} else {
throw new IncompatibleServerException();
}
- } else if (!this.streamFeatures.hasChild("register")
- && account.isOptionSet(Account.OPTION_REGISTER)) {
- forceCloseSocket();
- changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED);
+ } else if (!this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) {
+ throw new RegistrationNotSupportedException();
} else if (this.streamFeatures.hasChild("mechanisms")
&& shouldAuthenticate
&& (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS)) {
@@ -950,7 +975,7 @@ public class XmppConnection implements Runnable {
}
public void resetEverything() {
- resetAttemptCount();
+ resetAttemptCount(true);
resetStreamId();
clearIqCallbacks();
mStanzaQueue.clear();
@@ -1359,30 +1384,6 @@ public class XmppConnection implements Runnable {
}
}
- public void waitForPush() {
- if (tagWriter.isActive()) {
- tagWriter.finish();
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- while(!tagWriter.finished()) {
- Thread.sleep(10);
- }
- socket.close();
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream");
- changeStatus(Account.State.OFFLINE);
- } catch (IOException | InterruptedException e) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": error while closing socket for waitForPush()");
- }
- }
- }).start();
- } else {
- forceCloseSocket();
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)");
- }
- }
-
private void forceCloseSocket() {
if (socket != null) {
try {
@@ -1526,9 +1527,11 @@ public class XmppConnection implements Runnable {
this.sendPacket(new InactivePacket());
}
- public void resetAttemptCount() {
+ public void resetAttemptCount(boolean resetConnectTime) {
this.attempt = 0;
- this.lastConnect = 0;
+ if (resetConnectTime) {
+ this.lastConnect = 0;
+ }
}
public void setInteractive(boolean interactive) {
@@ -1567,6 +1570,10 @@ public class XmppConnection implements Runnable {
}
+ private class RegistrationNotSupportedException extends IOException {
+
+ }
+
public enum Identity {
FACEBOOK,
SLACK,
diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml
index ea4d49c8..6c26a162 100644
--- a/src/main/res/layout/activity_edit_account.xml
+++ b/src/main/res/layout/activity_edit_account.xml
@@ -610,6 +610,14 @@
android:orientation="vertical"
android:showDividers="middle">
</LinearLayout>
+ <Button
+ android:id="@+id/clear_devices"
+ style="?android:attr/borderlessButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/clear_other_devices"
+ android:layout_gravity="center_horizontal"
+ android:textColor="@color/accent"/>
</LinearLayout>
</LinearLayout>
</ScrollView>
diff --git a/src/main/res/menu/editaccount.xml b/src/main/res/menu/editaccount.xml
index 9ab1788b..5df7ee5b 100644
--- a/src/main/res/menu/editaccount.xml
+++ b/src/main/res/menu/editaccount.xml
@@ -44,10 +44,6 @@
android:title="@string/change_password"/>
<item
- android:id="@+id/action_clear_devices"
- android:showAsAction="never"
- android:title="@string/clear_other_devices"/>
- <item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:showAsAction="never"