Overhauled Message tagging

Messages are now tagged with the IdentityKey fingerprint of the
originating session. IdentityKeys have one of three trust states:
undecided (default), trusted, and untrusted/not yet trusted.
This commit is contained in:
Andreas Straub 2015-07-09 14:23:17 +02:00
parent 7f918542c8
commit d173913eba
5 changed files with 138 additions and 88 deletions

View file

@ -85,6 +85,7 @@ public class AxolotlService {
public static final String DEVICE_ID = "device_id";
public static final String ID = "id";
public static final String KEY = "key";
public static final String FINGERPRINT = "fingerprint";
public static final String NAME = "name";
public static final String TRUSTED = "trusted";
public static final String OWN = "ownkey";
@ -99,6 +100,23 @@ public class AxolotlService {
private final int localRegistrationId;
private int currentPreKeyId = 0;
public enum Trust {
UNDECIDED, // 0
TRUSTED,
UNTRUSTED;
public String toString() {
switch(this){
case UNDECIDED:
return "Trust undecided";
case TRUSTED:
return "Trusted";
case UNTRUSTED:
default:
return "Untrusted";
}
}
};
private static IdentityKeyPair generateIdentityKeyPair() {
Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Generating axolotl IdentityKeyPair...");
@ -242,11 +260,17 @@ public class AxolotlService {
*/
@Override
public boolean isTrustedIdentity(String name, IdentityKey identityKey) {
//Set<IdentityKey> trustedKeys = mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name);
//return trustedKeys.isEmpty() || trustedKeys.contains(identityKey);
return true;
}
public Trust getFingerprintTrust(String name, String fingerprint) {
return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, name, fingerprint);
}
public void setFingerprintTrust(String name, String fingerprint, Trust trust) {
mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, name, fingerprint, trust);
}
// --------------------------------------
// SessionStore
// --------------------------------------
@ -325,14 +349,6 @@ public class AxolotlService {
new AxolotlAddress(name, 0));
}
public boolean isTrustedSession(AxolotlAddress address) {
return mXmppConnectionService.databaseBackend.isTrustedSession(this.account, address);
}
public void setTrustedSession(AxolotlAddress address, boolean trusted) {
mXmppConnectionService.databaseBackend.setTrustedSession(this.account, address, trusted);
}
// --------------------------------------
// PreKeyStore
// --------------------------------------
@ -453,27 +469,22 @@ public class AxolotlService {
public static class XmppAxolotlSession {
private final SessionCipher cipher;
private boolean isTrusted = false;
private Integer preKeyId = null;
private final SQLiteAxolotlStore sqLiteAxolotlStore;
private final AxolotlAddress remoteAddress;
private final Account account;
private String fingerprint = null;
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) {
this(account, store, remoteAddress);
this.fingerprint = fingerprint;
}
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
this.cipher = new SessionCipher(store, remoteAddress);
this.remoteAddress = remoteAddress;
this.sqLiteAxolotlStore = store;
this.account = account;
this.isTrusted = sqLiteAxolotlStore.isTrustedSession(remoteAddress);
}
public void trust() {
sqLiteAxolotlStore.setTrustedSession(remoteAddress, true);
this.isTrusted = true;
}
public boolean isTrusted() {
return this.isTrusted;
}
public Integer getPreKeyId() {
@ -481,19 +492,30 @@ public class AxolotlService {
}
public void resetPreKeyId() {
preKeyId = null;
}
public String getFingerprint() {
return fingerprint;
}
public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) {
byte[] plaintext = null;
try {
try {
PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents());
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", "");
if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) {
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Had session with fingerprint "+ this.fingerprint+", received message with fingerprint "+fingerprint);
} else {
this.fingerprint = fingerprint;
plaintext = cipher.decrypt(message);
if (message.getPreKeyId().isPresent()) {
preKeyId = message.getPreKeyId().get();
}
}
} catch (InvalidMessageException | InvalidVersionException e) {
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"WhisperMessage received");
WhisperMessage message = new WhisperMessage(incomingHeader.getContents());
@ -582,7 +604,9 @@ public class AxolotlService {
List<Integer> deviceIDs = store.getSubDeviceSessions(address);
for (Integer deviceId : deviceIDs) {
AxolotlAddress axolotlAddress = new AxolotlAddress(address, deviceId);
this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress));
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building session for remote address: "+axolotlAddress.toString());
String fingerprint = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey().getFingerprint().replaceAll("\\s", "");
this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, fingerprint));
}
}
}
@ -619,18 +643,6 @@ public class AxolotlService {
return axolotlStore.getIdentityKeyPair().getPublicKey();
}
public void trustSession(AxolotlAddress counterpart) {
XmppAxolotlSession session = sessions.get(counterpart);
if (session != null) {
session.trust();
}
}
public boolean isTrustedSession(AxolotlAddress counterpart) {
XmppAxolotlSession session = sessions.get(counterpart);
return session != null && session.isTrusted();
}
private AxolotlAddress getAddressForJid(Jid jid) {
return new AxolotlAddress(jid.toString(), 0);
}
@ -808,11 +820,19 @@ public class AxolotlService {
}
public boolean isContactAxolotlCapable(Contact contact) {
Jid jid = contact.getJid().toBareJid();
AxolotlAddress address = new AxolotlAddress(jid.toString(), 0);
return sessions.hasAny(address) ||
( deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty());
}
public SQLiteAxolotlStore.Trust getFingerprintTrust(String name, String fingerprint) {
return axolotlStore.getFingerprintTrust(name, fingerprint);
}
public void setFingerprintTrust(String name, String fingerprint, SQLiteAxolotlStore.Trust trust) {
axolotlStore.setFingerprintTrust(name, fingerprint, trust);
}
private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address) {
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building new sesstion for " + address.getDeviceId());
@ -851,7 +871,7 @@ public class AxolotlService {
try {
SessionBuilder builder = new SessionBuilder(axolotlStore, address);
builder.process(preKeyBundle);
XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address);
XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey().getFingerprint().replaceAll("\\s", ""));
sessions.put(address, session);
fetchStatusMap.put(address, FetchStatus.SUCCESS);
} catch (UntrustedIdentityException|InvalidKeyException e) {
@ -1003,7 +1023,7 @@ public class AxolotlService {
byte[] payloadKey = session.processReceiving(header);
if (payloadKey != null) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl header. Decrypting message...");
plaintextMessage = message.decrypt(session, payloadKey);
plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint());
}
Integer preKeyId = session.getPreKeyId();
if (preKeyId != null) {

View file

@ -67,10 +67,12 @@ public class XmppAxolotlMessage {
public static class XmppAxolotlPlaintextMessage {
private final AxolotlService.XmppAxolotlSession session;
private final String plaintext;
private final String fingerprint;
public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext) {
public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext, String fingerprint) {
this.session = session;
this.plaintext = plaintext;
this.fingerprint = fingerprint;
}
public String getPlaintext() {
@ -81,6 +83,9 @@ public class XmppAxolotlMessage {
return session;
}
public String getFingerprint() {
return fingerprint;
}
}
public XmppAxolotlMessage(Jid from, Element axolotlMessage) {
@ -167,7 +172,7 @@ public class XmppAxolotlMessage {
}
public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key) {
public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) {
XmppAxolotlPlaintextMessage plaintextMessage = null;
try {
@ -178,7 +183,7 @@ public class XmppAxolotlMessage {
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
String plaintext = new String(cipher.doFinal(ciphertext));
plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext);
plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext, fingerprint);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| InvalidAlgorithmParameterException | IllegalBlockSizeException

View file

@ -54,6 +54,7 @@ public class Message extends AbstractEntity {
public static final String REMOTE_MSG_ID = "remoteMsgId";
public static final String SERVER_MSG_ID = "serverMsgId";
public static final String RELATIVE_FILE_PATH = "relativeFilePath";
public static final String FINGERPRINT = "axolotl_fingerprint";
public static final String ME_COMMAND = "/me ";
@ -67,7 +68,6 @@ public class Message extends AbstractEntity {
protected int encryption;
protected int status;
protected int type;
private AxolotlService.XmppAxolotlSession axolotlSession = null;
protected String relativeFilePath;
protected boolean read = true;
protected String remoteMsgId = null;
@ -76,6 +76,7 @@ public class Message extends AbstractEntity {
protected Transferable transferable = null;
private Message mNextMessage = null;
private Message mPreviousMessage = null;
private String axolotlFingerprint = null;
private Message() {
@ -97,6 +98,7 @@ public class Message extends AbstractEntity {
TYPE_TEXT,
null,
null,
null,
null);
this.conversation = conversation;
}
@ -104,7 +106,7 @@ public class Message extends AbstractEntity {
private Message(final String uuid, final String conversationUUid, final Jid counterpart,
final Jid trueCounterpart, final String body, final long timeSent,
final int encryption, final int status, final int type, final String remoteMsgId,
final String relativeFilePath, final String serverMsgId) {
final String relativeFilePath, final String serverMsgId, final String fingerprint) {
this.uuid = uuid;
this.conversationUuid = conversationUUid;
this.counterpart = counterpart;
@ -117,6 +119,7 @@ public class Message extends AbstractEntity {
this.remoteMsgId = remoteMsgId;
this.relativeFilePath = relativeFilePath;
this.serverMsgId = serverMsgId;
this.axolotlFingerprint = fingerprint;
}
public static Message fromCursor(Cursor cursor) {
@ -153,7 +156,8 @@ public class Message extends AbstractEntity {
cursor.getInt(cursor.getColumnIndex(TYPE)),
cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)),
cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)),
cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)));
cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)),
cursor.getString(cursor.getColumnIndex(FINGERPRINT)));
}
public static Message createStatusMessage(Conversation conversation, String body) {
@ -187,6 +191,7 @@ public class Message extends AbstractEntity {
values.put(REMOTE_MSG_ID, remoteMsgId);
values.put(RELATIVE_FILE_PATH, relativeFilePath);
values.put(SERVER_MSG_ID, serverMsgId);
values.put(FINGERPRINT, axolotlFingerprint);
return values;
}
@ -667,11 +672,7 @@ public class Message extends AbstractEntity {
public int height = 0;
}
public boolean isTrusted() {
return this.axolotlSession != null && this.axolotlSession.isTrusted();
}
public void setAxolotlSession(AxolotlService.XmppAxolotlSession session) {
this.axolotlSession = session;
public void setAxolotlFingerprint(String fingerprint) {
this.axolotlFingerprint = fingerprint;
}
}

View file

@ -106,7 +106,8 @@ public class MessageParser extends AbstractParser implements
XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage);
if(plaintextMessage != null) {
finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
finishedMessage.setAxolotlSession(plaintextMessage.getSession());
finishedMessage.setAxolotlFingerprint(plaintextMessage.getFingerprint());
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount())+" Received Message with session fingerprint: "+plaintextMessage.getFingerprint());
}
return finishedMessage;

View file

@ -82,7 +82,6 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, "
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
+ AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, "
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
@ -97,6 +96,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, "
+ AxolotlService.SQLiteAxolotlStore.OWN + " INTEGER, "
+ AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " TEXT PRIMARY KEY ON CONFLICT IGNORE, "
+ AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, "
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE "
@ -132,6 +133,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
+ Message.RELATIVE_FILE_PATH + " TEXT, "
+ Message.SERVER_MSG_ID + " TEXT, "
+ Message.FINGERPRINT + " TEXT, "
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
+ Message.CONVERSATION + ") REFERENCES "
+ Conversation.TABLENAME + "(" + Conversation.UUID
@ -284,6 +286,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
if (oldVersion < 15 && newVersion >= 15) {
recreateAxolotlDb();
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
+ Message.FINGERPRINT + " TEXT");
}
}
@ -645,28 +649,6 @@ public class DatabaseBackend extends SQLiteOpenHelper {
args);
}
public boolean isTrustedSession(Account account, AxolotlAddress contact) {
boolean trusted = false;
Cursor cursor = getCursorForSession(account, contact);
if(cursor.getCount() != 0) {
cursor.moveToFirst();
trusted = cursor.getInt(cursor.getColumnIndex(
AxolotlService.SQLiteAxolotlStore.TRUSTED)) > 0;
}
cursor.close();
return trusted;
}
public void setTrustedSession(Account account, AxolotlAddress contact, boolean trusted) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(AxolotlService.SQLiteAxolotlStore.NAME, contact.getName());
values.put(AxolotlService.SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trusted?1:0);
db.insert(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
}
private Cursor getCursorForPreKey(Account account, int preKeyId) {
SQLiteDatabase db = this.getReadableDatabase();
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
@ -796,17 +778,28 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
private Cursor getIdentityKeyCursor(Account account, String name, boolean own) {
return getIdentityKeyCursor(account, name, own, null);
}
private Cursor getIdentityKeyCursor(Account account, String name, boolean own, String fingerprint) {
final SQLiteDatabase db = this.getReadableDatabase();
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
String[] selectionArgs = {account.getUuid(),
name,
own?"1":"0"};
String[] columns = {AxolotlService.SQLiteAxolotlStore.TRUSTED,
AxolotlService.SQLiteAxolotlStore.KEY};
ArrayList<String> selectionArgs = new ArrayList<>(4);
selectionArgs.add(account.getUuid());
selectionArgs.add(name);
selectionArgs.add(own?"1":"0");
String selectionString = AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND "
+ AxolotlService.SQLiteAxolotlStore.OWN + " = ? ";
if (fingerprint != null){
selectionArgs.add(fingerprint);
selectionString += "AND " +AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ";
}
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME,
columns,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND "
+ AxolotlService.SQLiteAxolotlStore.OWN + " = ? ",
selectionArgs,
selectionString,
selectionArgs.toArray(new String[selectionArgs.size()]),
null, null, null);
return cursor;
@ -844,22 +837,52 @@ public class DatabaseBackend extends SQLiteOpenHelper {
return identityKeys;
}
private void storeIdentityKey(Account account, String name, boolean own, String base64Serialized) {
private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
values.put(AxolotlService.SQLiteAxolotlStore.NAME, name);
values.put(AxolotlService.SQLiteAxolotlStore.OWN, own ? 1 : 0);
values.put(AxolotlService.SQLiteAxolotlStore.FINGERPRINT, fingerprint);
values.put(AxolotlService.SQLiteAxolotlStore.KEY, base64Serialized);
db.insert(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
}
public AxolotlService.SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String name, String fingerprint) {
Cursor cursor = getIdentityKeyCursor(account, name, false, fingerprint);
AxolotlService.SQLiteAxolotlStore.Trust trust = null;
if (cursor.getCount() > 0) {
cursor.moveToFirst();
int trustValue = cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED));
trust = AxolotlService.SQLiteAxolotlStore.Trust.values()[trustValue];
}
cursor.close();
return trust;
}
public boolean setIdentityKeyTrust(Account account, String name, String fingerprint, AxolotlService.SQLiteAxolotlStore.Trust trust) {
SQLiteDatabase db = this.getWritableDatabase();
String[] selectionArgs = {
account.getUuid(),
name,
fingerprint
};
ContentValues values = new ContentValues();
values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trust.ordinal());
int rows = db.update(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? "
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? "
+ AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ",
selectionArgs);
return rows == 1;
}
public void storeIdentityKey(Account account, String name, IdentityKey identityKey) {
storeIdentityKey(account, name, false, Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
}
public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) {
storeIdentityKey(account, name, true, Base64.encodeToString(identityKeyPair.serialize(),Base64.DEFAULT));
storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT));
}
public void recreateAxolotlDb() {