Never build a session with oneself

If we detect our own ID is not in our own devicelist on receiving an
update, we reannounce ourselves. This used to have the side effect of
modifying the list of devices we thought were in the update set, causing
us to accidentally build a session with ourselves.

This lead to our own key being set to TRUSTED_INACTIVE, resulting in red
lock icons on messages sent by the own device.

We fix this by having publishOwnDeviceId() operate on a copy of the
original set. This commit also includes a db migration which deletes
sessions with oneself and sets own keys back to TRUSTED.
This commit is contained in:
Andreas Straub 2015-09-05 17:25:46 +02:00
parent a32a577e36
commit e2d506c96a
3 changed files with 68 additions and 20 deletions

View File

@ -337,9 +337,10 @@ public class AxolotlService {
} }
public void publishOwnDeviceId(Set<Integer> deviceIds) { public void publishOwnDeviceId(Set<Integer> deviceIds) {
if (!deviceIds.contains(getOwnDeviceId())) { Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
if (!deviceIdsCopy.contains(getOwnDeviceId())) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist."); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist.");
if (deviceIds.isEmpty()) { if (deviceIdsCopy.isEmpty()) {
if (numPublishTriesOnEmptyPep >= publishTriesThreshold) { if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting..."); Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting...");
pepBroken = true; pepBroken = true;
@ -351,8 +352,8 @@ public class AxolotlService {
} else { } else {
numPublishTriesOnEmptyPep = 0; numPublishTriesOnEmptyPep = 0;
} }
deviceIds.add(getOwnDeviceId()); deviceIdsCopy.add(getOwnDeviceId());
IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy);
mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
@Override @Override
public void onIqPacketReceived(Account account, IqPacket packet) { public void onIqPacketReceived(Account account, IqPacket packet) {
@ -490,7 +491,10 @@ public class AxolotlService {
} }
private void buildSessionFromPEP(final AxolotlAddress address) { private void buildSessionFromPEP(final AxolotlAddress address) {
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.getDeviceId()); Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.toString());
if (address.getDeviceId() == getOwnDeviceId()) {
throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
}
try { try {
IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice( IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(

View File

@ -89,15 +89,14 @@ public class SQLiteAxolotlStore implements AxolotlStore {
private IdentityKeyPair loadIdentityKeyPair() { private IdentityKeyPair loadIdentityKeyPair() {
String ownName = account.getJid().toBareJid().toString(); String ownName = account.getJid().toBareJid().toString();
IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account, IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account);
ownName);
if (ownKey != null) { if (ownKey != null) {
return ownKey; return ownKey;
} else { } else {
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl key for account " + ownName); Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve own IdentityKeyPair");
ownKey = generateIdentityKeyPair(); ownKey = generateIdentityKeyPair();
mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownName, ownKey); mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownKey);
} }
return ownKey; return ownKey;
} }

View File

@ -42,7 +42,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private static DatabaseBackend instance = null; private static DatabaseBackend instance = null;
private static final String DATABASE_NAME = "history"; private static final String DATABASE_NAME = "history";
private static final int DATABASE_VERSION = 16; private static final int DATABASE_VERSION = 17;
private static String CREATE_CONTATCS_STATEMENT = "create table " private static String CREATE_CONTATCS_STATEMENT = "create table "
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@ -301,6 +301,20 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
+ Message.CARBON + " INTEGER"); + Message.CARBON + " INTEGER");
} }
if (oldVersion < 17 && newVersion >= 17) {
List<Account> accounts = getAccounts(db);
for (Account account : accounts) {
String ownDeviceIdString = account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID);
if ( ownDeviceIdString == null ) {
continue;
}
int ownDeviceId = Integer.valueOf(ownDeviceIdString);
AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), ownDeviceId);
deleteSession(db, account, ownAddress);
IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account);
setIdentityKeyTrust(db, account, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), XmppAxolotlSession.Trust.TRUSTED );
}
}
} }
public static synchronized DatabaseBackend getInstance(Context context) { public static synchronized DatabaseBackend getInstance(Context context) {
@ -414,8 +428,12 @@ public class DatabaseBackend extends SQLiteOpenHelper {
} }
public List<Account> getAccounts() { public List<Account> getAccounts() {
List<Account> list = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase(); SQLiteDatabase db = this.getReadableDatabase();
return getAccounts(db);
}
private List<Account> getAccounts(SQLiteDatabase db) {
List<Account> list = new ArrayList<>();
Cursor cursor = db.query(Account.TABLENAME, null, null, null, null, Cursor cursor = db.query(Account.TABLENAME, null, null, null, null,
null, null); null, null);
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
@ -602,8 +620,12 @@ public class DatabaseBackend extends SQLiteOpenHelper {
} }
public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) { public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
List<Integer> devices = new ArrayList<>();
final SQLiteDatabase db = this.getReadableDatabase(); final SQLiteDatabase db = this.getReadableDatabase();
return getSubDeviceSessions(db, account, contact);
}
private List<Integer> getSubDeviceSessions(SQLiteDatabase db, Account account, AxolotlAddress contact) {
List<Integer> devices = new ArrayList<>();
String[] columns = {SQLiteAxolotlStore.DEVICE_ID}; String[] columns = {SQLiteAxolotlStore.DEVICE_ID};
String[] selectionArgs = {account.getUuid(), String[] selectionArgs = {account.getUuid(),
contact.getName()}; contact.getName()};
@ -642,6 +664,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void deleteSession(Account account, AxolotlAddress contact) { public void deleteSession(Account account, AxolotlAddress contact) {
SQLiteDatabase db = this.getWritableDatabase(); SQLiteDatabase db = this.getWritableDatabase();
deleteSession(db, account, contact);
}
private void deleteSession(SQLiteDatabase db, Account account, AxolotlAddress contact) {
String[] args = {account.getUuid(), String[] args = {account.getUuid(),
contact.getName(), contact.getName(),
Integer.toString(contact.getDeviceId())}; Integer.toString(contact.getDeviceId())};
@ -790,15 +816,24 @@ public class DatabaseBackend extends SQLiteOpenHelper {
} }
private Cursor getIdentityKeyCursor(Account account, String name, boolean own) { private Cursor getIdentityKeyCursor(Account account, String name, boolean own) {
return getIdentityKeyCursor(account, name, own, null); final SQLiteDatabase db = this.getReadableDatabase();
return getIdentityKeyCursor(db, account, name, own);
}
private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) {
return getIdentityKeyCursor(db, account, name, own, null);
} }
private Cursor getIdentityKeyCursor(Account account, String fingerprint) { private Cursor getIdentityKeyCursor(Account account, String fingerprint) {
return getIdentityKeyCursor(account, null, null, fingerprint); final SQLiteDatabase db = this.getReadableDatabase();
return getIdentityKeyCursor(db, account, fingerprint);
} }
private Cursor getIdentityKeyCursor(Account account, String name, Boolean own, String fingerprint) { private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String fingerprint) {
final SQLiteDatabase db = this.getReadableDatabase(); return getIdentityKeyCursor(db, account, null, null, fingerprint);
}
private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) {
String[] columns = {SQLiteAxolotlStore.TRUSTED, String[] columns = {SQLiteAxolotlStore.TRUSTED,
SQLiteAxolotlStore.KEY}; SQLiteAxolotlStore.KEY};
ArrayList<String> selectionArgs = new ArrayList<>(4); ArrayList<String> selectionArgs = new ArrayList<>(4);
@ -825,9 +860,15 @@ public class DatabaseBackend extends SQLiteOpenHelper {
return cursor; return cursor;
} }
public IdentityKeyPair loadOwnIdentityKeyPair(Account account, String name) { public IdentityKeyPair loadOwnIdentityKeyPair(Account account) {
SQLiteDatabase db = getReadableDatabase();
return loadOwnIdentityKeyPair(db, account);
}
private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) {
String name = account.getJid().toBareJid().toString();
IdentityKeyPair identityKeyPair = null; IdentityKeyPair identityKeyPair = null;
Cursor cursor = getIdentityKeyCursor(account, name, true); Cursor cursor = getIdentityKeyCursor(db, account, name, true);
if(cursor.getCount() != 0) { if(cursor.getCount() != 0) {
cursor.moveToFirst(); cursor.moveToFirst();
try { try {
@ -911,6 +952,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) { public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
SQLiteDatabase db = this.getWritableDatabase(); SQLiteDatabase db = this.getWritableDatabase();
return setIdentityKeyTrust(db, account, fingerprint, trust);
}
private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
String[] selectionArgs = { String[] selectionArgs = {
account.getUuid(), account.getUuid(),
fingerprint fingerprint
@ -928,8 +973,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
} }
public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) { public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) {
storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED); storeIdentityKey(account, account.getJid().toBareJid().toString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED);
} }
public void recreateAxolotlDb() { public void recreateAxolotlDb() {