aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/eu/siacs/conversations
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/eu/siacs/conversations')
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java37
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java10
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java25
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Contact.java5
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Conversation.java11
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java66
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java10
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java45
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationActivity.java5
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java2
-rw-r--r--src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java2
-rw-r--r--src/main/java/eu/siacs/conversations/ui/XmppActivity.java21
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java13
-rw-r--r--src/main/java/eu/siacs/conversations/utils/CryptoHelper.java75
14 files changed, 258 insertions, 69 deletions
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 e22b05c00..31b23e3cb 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
@@ -146,8 +146,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
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));
+ IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
+ this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
}
}
@@ -575,12 +575,18 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return axolotlStore.getFingerprintTrust(fingerprint);
}
+ public X509Certificate getFingerprintCertificate(String fingerprint) {
+ return axolotlStore.getFingerprintCertificate(fingerprint);
+ }
+
public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
axolotlStore.setFingerprintTrust(fingerprint, trust);
}
- private void verifySessionWithPEP(final XmppAxolotlSession session, final IdentityKey identityKey) {
+ private void verifySessionWithPEP(final XmppAxolotlSession session) {
+ Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
final AxolotlAddress address = session.getRemoteAddress();
+ final IdentityKey identityKey = session.getIdentityKey();
try {
IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId());
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
@@ -595,8 +601,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (verifier.verify(verification.second)) {
try {
mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
- Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+session.getFingerprint());
- setFingerprintTrust(session.getFingerprint(), XmppAxolotlSession.Trust.TRUSTED_X509);
+ String fingerprint = session.getFingerprint();
+ Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint);
+ setFingerprintTrust(fingerprint, XmppAxolotlSession.Trust.TRUSTED_X509);
+ axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
finishBuildingSessionsFromPEP(address);
return;
@@ -607,6 +615,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} catch (Exception e) {
Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
}
+ } else {
+ Log.d(Config.LOGTAG,"no verification found");
}
fetchStatusMap.put(address, FetchStatus.SUCCESS);
finishBuildingSessionsFromPEP(address);
@@ -678,10 +688,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
try {
SessionBuilder builder = new SessionBuilder(axolotlStore, address);
builder.process(preKeyBundle);
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey().getFingerprint().replaceAll("\\s", ""));
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
sessions.put(address, session);
if (Config.X509_VERIFICATION) {
- verifySessionWithPEP(session, bundle.getIdentityKey());
+ verifySessionWithPEP(session);
} else {
fetchStatusMap.put(address, FetchStatus.SUCCESS);
finishBuildingSessionsFromPEP(address);
@@ -718,7 +728,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
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", ""));
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
sessions.put(address, session);
} else {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + foreignId);
@@ -740,7 +750,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
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", ""));
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
sessions.put(address, session);
} else {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
@@ -889,8 +899,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) {
IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
return (identityKey != null)
- ? new XmppAxolotlSession(account, axolotlStore, address,
- identityKey.getFingerprint().replaceAll("\\s", ""))
+ ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
: null;
}
@@ -944,11 +953,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
private void putFreshSession(XmppAxolotlSession session) {
+ Log.d(Config.LOGTAG,"put fresh session");
sessions.put(session);
if (Config.X509_VERIFICATION) {
- IdentityKey identityKey = axolotlStore.loadSession(session.getRemoteAddress()).getSessionState().getRemoteIdentityKey();
- if (identityKey != null) {
- verifySessionWithPEP(session, identityKey);
+ if (session.getIdentityKey() != null) {
+ verifySessionWithPEP(session);
} else {
Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification");
}
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 a78317183..3c8cb3c10 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
@@ -15,6 +15,7 @@ import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import org.whispersystems.libaxolotl.util.KeyHelper;
+import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Set;
@@ -36,6 +37,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
public static final String NAME = "name";
public static final String TRUSTED = "trusted";
public static final String OWN = "ownkey";
+ public static final String CERTIFICATE = "certificate";
public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id";
public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id";
@@ -213,6 +215,14 @@ public class SQLiteAxolotlStore implements AxolotlStore {
trustCache.remove(fingerprint);
}
+ public void setFingerprintCertificate(String fingerprint, X509Certificate x509Certificate) {
+ mXmppConnectionService.databaseBackend.setIdentityKeyCertificate(account, fingerprint, x509Certificate);
+ }
+
+ public X509Certificate getFingerprintCertificate(String fingerprint) {
+ return mXmppConnectionService.databaseBackend.getIdentityKeyCertifcate(account, fingerprint);
+ }
+
public Set<IdentityKey> 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 c452acfd4..b713eb5fe 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
@@ -6,6 +6,7 @@ import android.util.Log;
import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.DuplicateMessageException;
+import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.InvalidMessageException;
@@ -29,7 +30,7 @@ public class XmppAxolotlSession {
private final SQLiteAxolotlStore sqLiteAxolotlStore;
private final AxolotlAddress remoteAddress;
private final Account account;
- private String fingerprint = null;
+ private IdentityKey identityKey;
private Integer preKeyId = null;
private boolean fresh = true;
@@ -103,9 +104,9 @@ public class XmppAxolotlSession {
}
}
- public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) {
+ public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, IdentityKey identityKey) {
this(account, store, remoteAddress);
- this.fingerprint = fingerprint.replaceAll("\\s","");
+ this.identityKey = identityKey;
}
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
@@ -125,7 +126,11 @@ public class XmppAxolotlSession {
}
public String getFingerprint() {
- return fingerprint;
+ return identityKey == null ? null : identityKey.getFingerprint().replaceAll("\\s", "");
+ }
+
+ public IdentityKey getIdentityKey() {
+ return identityKey;
}
public AxolotlAddress getRemoteAddress() {
@@ -141,11 +146,11 @@ public class XmppAxolotlSession {
}
protected void setTrust(Trust trust) {
- sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust);
+ sqLiteAxolotlStore.setFingerprintTrust(getFingerprint(), trust);
}
protected Trust getTrust() {
- Trust trust = sqLiteAxolotlStore.getFingerprintTrust(fingerprint);
+ Trust trust = sqLiteAxolotlStore.getFingerprintTrust(getFingerprint());
return (trust == null) ? Trust.UNDECIDED : trust;
}
@@ -164,11 +169,11 @@ public class XmppAxolotlSession {
try {
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)) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.fingerprint + ", received message with fingerprint " + fingerprint);
+ IdentityKey msgIdentityKey = message.getIdentityKey();
+ if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) {
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint());
} else {
- this.fingerprint = fingerprint;
+ this.identityKey = msgIdentityKey;
plaintext = cipher.decrypt(message);
if (message.getPreKeyId().isPresent()) {
preKeyId = message.getPreKeyId().get();
diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java
index f5fa88d7a..9c8beb1ec 100644
--- a/src/main/java/eu/siacs/conversations/entities/Contact.java
+++ b/src/main/java/eu/siacs/conversations/entities/Contact.java
@@ -11,6 +11,7 @@ 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;
@@ -104,7 +105,9 @@ public class Contact implements ListItem, Blockable {
}
public String getDisplayName() {
- if (this.systemName != null) {
+ if (this.presenceName != null && Config.X509_VERIFICATION) {
+ return this.presenceName;
+ } else if (this.systemName != null) {
return this.systemName;
} else if (this.serverName != null) {
return this.serverName;
diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java
index 81f789f8d..bc7f302ae 100644
--- a/src/main/java/eu/siacs/conversations/entities/Conversation.java
+++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java
@@ -21,6 +21,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.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -621,8 +622,16 @@ public class Conversation extends AbstractEntity implements Blockable {
}
public int getNextEncryption() {
+ final AxolotlService axolotlService = getAccount().getAxolotlService();
int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
if (next == -1) {
+ if (Config.X509_VERIFICATION && mode == MODE_SINGLE) {
+ if (axolotlService != null && axolotlService.isContactAxolotlCapable(getContact())) {
+ return Message.ENCRYPTION_AXOLOTL;
+ } else {
+ return Message.ENCRYPTION_NONE;
+ }
+ }
int outgoing = this.getMostRecentlyUsedOutgoingEncryption();
if (outgoing == Message.ENCRYPTION_NONE) {
next = this.getMostRecentlyUsedIncomingEncryption();
@@ -631,7 +640,7 @@ public class Conversation extends AbstractEntity implements Blockable {
}
}
if (Config.FORCE_ENCRYPTION && mode == MODE_SINGLE && next <= 0) {
- if (getAccount().getAxolotlService().isContactAxolotlCapable(getContact())) {
+ if (axolotlService != null && axolotlService.isContactAxolotlCapable(getContact())) {
return Message.ENCRYPTION_AXOLOTL;
} else {
return Message.ENCRYPTION_OTR;
diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
index e482f0f85..3077c4889 100644
--- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -19,7 +19,12 @@ import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
@@ -44,7 +49,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private static DatabaseBackend instance = null;
private static final String DATABASE_NAME = "history";
- private static final int DATABASE_VERSION = 21;
+ private static final int DATABASE_VERSION = 22;
private static String CREATE_CONTATCS_STATEMENT = "create table "
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@@ -102,6 +107,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ SQLiteAxolotlStore.NAME + " TEXT, "
+ SQLiteAxolotlStore.OWN + " INTEGER, "
+ SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
+ + SQLiteAxolotlStore.CERTIFICATE + " BLOB, "
+ SQLiteAxolotlStore.TRUSTED + " INTEGER, "
+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ SQLiteAxolotlStore.ACCOUNT
@@ -345,6 +351,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ "=?", new String[]{account.getUuid()});
}
}
+
+ if (oldVersion < 22 && newVersion >= 22) {
+ db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE);
+ }
}
public static synchronized DatabaseBackend getInstance(Context context) {
@@ -592,16 +602,16 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
}
- public Pair<Long,String> getLastMessageReceived(Account account) {
+ public Pair<Long, String> getLastMessageReceived(Account account) {
SQLiteDatabase db = this.getReadableDatabase();
String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1";
String[] args = {account.getUuid()};
- Cursor cursor = db.rawQuery(sql,args);
- if (cursor.getCount() ==0) {
+ Cursor cursor = db.rawQuery(sql, args);
+ if (cursor.getCount() == 0) {
return null;
} else {
cursor.moveToFirst();
- return new Pair<>(cursor.getLong(0),cursor.getString(1));
+ return new Pair<>(cursor.getLong(0), cursor.getString(1));
}
}
@@ -1050,6 +1060,52 @@ public class DatabaseBackend extends SQLiteOpenHelper {
return rows == 1;
}
+ public boolean setIdentityKeyCertificate(Account account, String fingerprint, X509Certificate x509Certificate) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] selectionArgs = {
+ account.getUuid(),
+ fingerprint
+ };
+ try {
+ ContentValues values = new ContentValues();
+ values.put(SQLiteAxolotlStore.CERTIFICATE, x509Certificate.getEncoded());
+ return db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
+ SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
+ selectionArgs) == 1;
+ } catch (CertificateEncodingException e) {
+ Log.d(Config.LOGTAG, "could not encode certificate");
+ return false;
+ }
+ }
+
+ public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ String[] selectionArgs = {
+ account.getUuid(),
+ fingerprint
+ };
+ String[] colums = {SQLiteAxolotlStore.CERTIFICATE};
+ String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? ";
+ Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null);
+ if (cursor.getCount() < 1) {
+ return null;
+ } else {
+ cursor.moveToFirst();
+ byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE));
+ if (certificate == null || certificate.length == 0) {
+ return null;
+ }
+ try {
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate));
+ } catch (CertificateException e) {
+ Log.d(Config.LOGTAG,"certificate exception "+e.getMessage());
+ return null;
+ }
+ }
+ }
+
public void storeIdentityKey(Account account, String name, IdentityKey identityKey) {
storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
}
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index 649ddacd8..28d6e3550 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -50,6 +50,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
@@ -992,13 +993,16 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void onIqPacketReceived(final Account account, final IqPacket packet) {
if (packet.getType() == IqPacket.TYPE.RESULT) {
final Element query = packet.query();
- final List<Bookmark> bookmarks = new CopyOnWriteArrayList<>();
+ final HashMap<Jid, Bookmark> bookmarks = new HashMap<>();
final Element storage = query.findChild("storage", "storage:bookmarks");
if (storage != null) {
for (final Element item : storage.getChildren()) {
if (item.getName().equals("conference")) {
final Bookmark bookmark = Bookmark.parse(item, account);
- bookmarks.add(bookmark);
+ Bookmark old = bookmarks.put(bookmark.getJid(), bookmark);
+ if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) {
+ bookmark.setBookmarkName(old.getBookmarkName());
+ }
Conversation conversation = find(bookmark);
if (conversation != null) {
conversation.setBookmark(bookmark);
@@ -1011,7 +1015,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
}
- account.setBookmarks(bookmarks);
+ account.setBookmarks(new ArrayList<>(bookmarks.values()));
} else {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not fetch bookmarks");
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
index aff50502c..70cd982b1 100644
--- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -14,6 +14,7 @@ import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -27,15 +28,18 @@ import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.QuickContactBadge;
import android.widget.TextView;
+import android.widget.Toast;
import org.openintents.openpgp.util.OpenPgpUtils;
+import java.security.cert.X509Certificate;
import java.util.List;
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.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem;
@@ -394,7 +398,12 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
}
for (final String fingerprint : contact.getAccount().getAxolotlService().getFingerprintsForContact(contact)) {
boolean highlight = fingerprint.equals(messageFingerprint);
- hasKeys |= addFingerprintRow(keys, contact.getAccount(), fingerprint, highlight);
+ hasKeys |= addFingerprintRow(keys, contact.getAccount(), fingerprint, highlight, new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onOmemoKeyClicked(contact.getAccount(), fingerprint);
+ }
+ });
}
if (contact.getPgpKeyId() != 0) {
hasKeys = true;
@@ -446,6 +455,40 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
}
}
+ private void onOmemoKeyClicked(Account account, String fingerprint) {
+ final XmppAxolotlSession.Trust trust = account.getAxolotlService().getFingerprintTrust(fingerprint);
+ if (trust != null && trust == XmppAxolotlSession.Trust.TRUSTED_X509) {
+ X509Certificate x509Certificate = account.getAxolotlService().getFingerprintCertificate(fingerprint);
+ if (x509Certificate != null) {
+ showCertificateInformationDialog(CryptoHelper.extractCertificateInformation(x509Certificate));
+ } else {
+ Toast.makeText(this,R.string.certificate_not_found, Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ private void showCertificateInformationDialog(Bundle bundle) {
+ View view = getLayoutInflater().inflate(R.layout.certificate_information, null);
+ final String not_available = getString(R.string.certicate_info_not_available);
+ TextView subject_cn = (TextView) view.findViewById(R.id.subject_cn);
+ TextView subject_o = (TextView) view.findViewById(R.id.subject_o);
+ TextView issuer_cn = (TextView) view.findViewById(R.id.issuer_cn);
+ TextView issuer_o = (TextView) view.findViewById(R.id.issuer_o);
+ TextView sha1 = (TextView) view.findViewById(R.id.sha1);
+
+ subject_cn.setText(bundle.getString("subject_cn", not_available));
+ subject_o.setText(bundle.getString("subject_o", not_available));
+ issuer_cn.setText(bundle.getString("issuer_cn", not_available));
+ issuer_o.setText(bundle.getString("issuer_o", not_available));
+ sha1.setText(bundle.getString("sha1", not_available));
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.certificate_information);
+ builder.setView(view);
+ builder.setPositiveButton(R.string.ok, null);
+ builder.create().show();
+ }
+
protected void confirmToDeleteFingerprint(final String fingerprint) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.delete_fingerprint);
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index 47edd1a42..706a2cbd3 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -468,7 +468,7 @@ public class ConversationActivity extends XmppActivity
menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable() && getSelectedConversation().getMucOptions().participating());
menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite());
menuSecure.setVisible(!Config.HIDE_ENCRYPTION_IN_UI);
- menuSecure.setVisible(!Config.HIDE_PGP_IN_UI); //if pgp is hidden conferences have no choice of encryption
+ menuSecure.setVisible(!Config.HIDE_PGP_IN_UI && !Config.X509_VERIFICATION); //if pgp is hidden conferences have no choice of encryption
} else {
menuMucDetails.setVisible(false);
}
@@ -922,8 +922,9 @@ 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);
+ pgp.setVisible(!Config.HIDE_PGP_IN_UI && !Config.X509_VERIFICATION);
none.setVisible(!Config.FORCE_ENCRYPTION);
+ otr.setVisible(!Config.X509_VERIFICATION);
if (conversation.getMode() == Conversation.MODE_MULTI) {
otr.setVisible(false);
axolotl.setVisible(false);
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index 439ada3a6..f5356d24f 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -710,7 +710,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
continue;
}
boolean highlight = fingerprint.equals(messageFingerprint);
- hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight);
+ hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight, null);
}
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 4bd0d42da..2e6d32466 100644
--- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
@@ -126,6 +126,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
// own fingerprints have no impact on locked status.
}
},
+ null,
null
);
}
@@ -140,6 +141,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
lockOrUnlockAsNeeded();
}
},
+ null,
null
);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index 4ca18cf8e..d05f50817 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -651,7 +651,7 @@ public abstract class XmppActivity extends Activity {
builder.create().show();
}
- protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight) {
+ protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight, View.OnClickListener onKeyClickedListener) {
final XmppAxolotlSession.Trust trust = account.getAxolotlService()
.getFingerprintTrust(fingerprint);
if (trust == null) {
@@ -673,7 +673,8 @@ public abstract class XmppActivity extends Activity {
XmppAxolotlSession.Trust.UNTRUSTED);
v.setEnabled(true);
}
- }
+ },
+ onKeyClickedListener
);
}
@@ -685,24 +686,30 @@ public abstract class XmppActivity extends Activity {
boolean showTag,
CompoundButton.OnCheckedChangeListener
onCheckedChangeListener,
- View.OnClickListener onClickListener) {
+ View.OnClickListener onClickListener,
+ View.OnClickListener onKeyClickedListener) {
if (trust == XmppAxolotlSession.Trust.COMPROMISED) {
return false;
}
View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
TextView key = (TextView) view.findViewById(R.id.key);
+ key.setOnClickListener(onKeyClickedListener);
TextView keyType = (TextView) view.findViewById(R.id.key_type);
+ keyType.setOnClickListener(onKeyClickedListener);
Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust);
trustToggle.setVisibility(View.VISIBLE);
trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);
trustToggle.setOnClickListener(onClickListener);
- view.setOnLongClickListener(new View.OnLongClickListener() {
+ final View.OnLongClickListener purge = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
showPurgeKeyDialog(account, fingerprint);
return true;
}
- });
+ };
+ view.setOnLongClickListener(purge);
+ key.setOnLongClickListener(purge);
+ keyType.setOnLongClickListener(purge);
boolean x509 = trust == XmppAxolotlSession.Trust.TRUSTED_X509 || trust == XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509;
switch (trust) {
case UNTRUSTED:
@@ -752,7 +759,7 @@ public abstract class XmppActivity extends Activity {
keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
}
- key.setText(CryptoHelper.prettifyFingerprint(fingerprint));
+ key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
keys.addView(view);
return true;
}
@@ -762,7 +769,7 @@ public abstract class XmppActivity extends Activity {
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(fingerprint)
+ + "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2))
+ "\n\n" + getString(R.string.purge_key_desc_part2));
builder.setNegativeButton(getString(R.string.cancel), null);
builder.setPositiveButton(getString(R.string.accept),
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 a3cea867f..a54407dec 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
@@ -18,6 +18,7 @@ import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.util.DisplayMetrics;
+import android.util.Patterns;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
@@ -32,11 +33,11 @@ import android.widget.Toast;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
+import java.util.regex.Matcher;
import eu.siacs.conversations.R;
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;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
@@ -246,6 +247,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.messageBody.setText(text);
viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false));
viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
+ viewHolder.messageBody.setTextIsSelectable(false);
}
private void displayDecryptionFailed(ViewHolder viewHolder, boolean darkBackground) {
@@ -258,6 +260,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
R.string.decryption_failed));
viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false));
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
+ viewHolder.messageBody.setTextIsSelectable(false);
}
private void displayHeartMessage(final ViewHolder viewHolder, final String body) {
@@ -332,13 +335,21 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
viewHolder.messageBody.setText(span);
}
+ int urlCount = 0;
+ Matcher matcher = Patterns.WEB_URL.matcher(body);
+ while (matcher.find()) {
+ urlCount++;
+ }
+ viewHolder.messageBody.setTextIsSelectable(urlCount <= 1);
} else {
viewHolder.messageBody.setText("");
+ viewHolder.messageBody.setTextIsSelectable(false);
}
viewHolder.messageBody.setTextColor(this.getMessageTextColor(darkBackground, true));
viewHolder.messageBody.setLinkTextColor(this.getMessageTextColor(darkBackground, true));
viewHolder.messageBody.setHighlightColor(activity.getResources().getColor(darkBackground ? R.color.grey800 : R.color.grey500));
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
+ viewHolder.messageBody.setOnLongClickListener(openContextMenu);
}
private void displayDownloadableMessage(ViewHolder viewHolder,
diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
index ab407249c..1ef5fb3f6 100644
--- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
@@ -1,19 +1,17 @@
package eu.siacs.conversations.utils;
-import android.util.Log;
+import android.os.Bundle;
import android.util.Pair;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
-import org.bouncycastle.jce.PrincipalUtil;
-import java.security.SecureRandom;
+import java.security.MessageDigest;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
-import java.security.cert.X509Extension;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -21,6 +19,7 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Locale;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
@@ -31,8 +30,6 @@ import eu.siacs.conversations.xmpp.jid.Jid;
public final class CryptoHelper {
public static final String FILETRANSFER = "?FILETRANSFERv1:";
private final static char[] hexArray = "0123456789abcdef".toCharArray();
- private final static char[] vowels = "aeiou".toCharArray();
- private final static char[] consonants = "bcdfghjklmnpqrstvwxyz".toCharArray();
final public static byte[] ONE = new byte[] { 0, 0, 0, 1 };
public static String bytesToHex(byte[] bytes) {
@@ -66,22 +63,6 @@ public final class CryptoHelper {
return result;
}
- public static String randomMucName(SecureRandom random) {
- return randomWord(3, random) + "." + randomWord(7, random);
- }
-
- private static String randomWord(int lenght, SecureRandom random) {
- StringBuilder builder = new StringBuilder(lenght);
- for (int i = 0; i < lenght; ++i) {
- if (i % 2 == 0) {
- builder.append(consonants[random.nextInt(consonants.length)]);
- } else {
- builder.append(vowels[random.nextInt(vowels.length)]);
- }
- }
- return builder.toString();
- }
-
/**
* Escapes usernames or passwords for SASL.
*/
@@ -114,13 +95,21 @@ public final class CryptoHelper {
} else if (fingerprint.length() < 40) {
return fingerprint;
}
- StringBuilder builder = new StringBuilder(fingerprint.replaceAll("\\s",""));
+ StringBuilder builder = new StringBuilder(fingerprint.toLowerCase(Locale.US).replaceAll("\\s", ""));
for(int i=8;i<builder.length();i+=9) {
builder.insert(i, ' ');
}
return builder.toString();
}
+ public static String prettifyFingerprintCert(String fingerprint) {
+ StringBuilder builder = new StringBuilder(fingerprint);
+ for(int i=2;i < builder.length(); i+=3) {
+ builder.insert(i,':');
+ }
+ return builder.toString();
+ }
+
public static String[] getOrderedCipherSuites(final String[] platformSupportedCipherSuites) {
final Collection<String> cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS));
final List<String> platformCiphers = Arrays.asList(platformSupportedCipherSuites);
@@ -167,6 +156,46 @@ public final class CryptoHelper {
}
}
+ public static Bundle extractCertificateInformation(X509Certificate certificate) {
+ Bundle information = new Bundle();
+ try {
+ JcaX509CertificateHolder holder = new JcaX509CertificateHolder(certificate);
+ X500Name subject = holder.getSubject();
+ try {
+ information.putString("subject_cn", subject.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString());
+ } catch (Exception e) {
+ //ignored
+ }
+ try {
+ information.putString("subject_o",subject.getRDNs(BCStyle.O)[0].getFirst().getValue().toString());
+ } catch (Exception e) {
+ //ignored
+ }
+
+ X500Name issuer = holder.getIssuer();
+ try {
+ information.putString("issuer_cn", issuer.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString());
+ } catch (Exception e) {
+ //ignored
+ }
+ try {
+ information.putString("issuer_o", issuer.getRDNs(BCStyle.O)[0].getFirst().getValue().toString());
+ } catch (Exception e) {
+ //ignored
+ }
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ byte[] fingerprint = md.digest(certificate.getEncoded());
+ information.putString("sha1", prettifyFingerprintCert(bytesToHex(fingerprint)));
+ } catch (Exception e) {
+
+ }
+ return information;
+ } catch (CertificateEncodingException e) {
+ return information;
+ }
+ }
+
public static int encryptionTypeToText(int encryption) {
switch (encryption) {
case Message.ENCRYPTION_OTR: