Refactor out inner classes, cache trust store

Moves SQLiteAxolotlStore and XmppAxolotlSession into proper classes.

IdentityKeys trust statuses are now cached in an LruCache to prevent
hammering the database when rendering the UI.
This commit is contained in:
Andreas Straub 2015-07-28 22:00:54 +02:00
parent d9bdce0104
commit efcefc2e63
10 changed files with 760 additions and 709 deletions

View File

@ -6,28 +6,15 @@ import android.util.Log;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.whispersystems.libaxolotl.AxolotlAddress; import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair; import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException; import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidKeyIdException; import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.SessionBuilder; import org.whispersystems.libaxolotl.SessionBuilder;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.UntrustedIdentityException; import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPublicKey; import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.state.PreKeyBundle; import org.whispersystems.libaxolotl.state.PreKeyBundle;
import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import org.whispersystems.libaxolotl.util.KeyHelper; import org.whispersystems.libaxolotl.util.KeyHelper;
@ -73,547 +60,6 @@ public class AxolotlService {
private final FetchStatusMap fetchStatusMap; private final FetchStatusMap fetchStatusMap;
private final SerialSingleThreadExecutor executor; private final SerialSingleThreadExecutor executor;
public static class SQLiteAxolotlStore implements AxolotlStore {
public static final String PREKEY_TABLENAME = "prekeys";
public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys";
public static final String SESSION_TABLENAME = "sessions";
public static final String IDENTITIES_TABLENAME = "identities";
public static final String ACCOUNT = "account";
public static final String DEVICE_ID = "device_id";
public static final String ID = "id";
public static final String KEY = "key";
public static final String FINGERPRINT = "fingerprint";
public static final String NAME = "name";
public static final String TRUSTED = "trusted";
public static final String OWN = "ownkey";
public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id";
public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id";
private final Account account;
private final XmppConnectionService mXmppConnectionService;
private IdentityKeyPair identityKeyPair;
private int localRegistrationId;
private int currentPreKeyId = 0;
public enum Trust {
UNDECIDED(0),
TRUSTED(1),
UNTRUSTED(2),
COMPROMISED(3),
INACTIVE(4);
private static final Map<Integer, Trust> trustsByValue = new HashMap<>();
static {
for (Trust trust : Trust.values()) {
trustsByValue.put(trust.getCode(), trust);
}
}
private final int code;
Trust(int code){
this.code = code;
}
public int getCode() {
return this.code;
}
public String toString() {
switch(this){
case UNDECIDED:
return "Trust undecided "+getCode();
case TRUSTED:
return "Trusted "+getCode();
case COMPROMISED:
return "Compromised "+getCode();
case INACTIVE:
return "Inactive "+getCode();
case UNTRUSTED:
default:
return "Untrusted "+getCode();
}
}
public static Trust fromBoolean(Boolean trusted) {
return trusted?TRUSTED:UNTRUSTED;
}
public static Trust fromCode(int code) {
return trustsByValue.get(code);
}
};
private static IdentityKeyPair generateIdentityKeyPair() {
Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Generating axolotl IdentityKeyPair...");
ECKeyPair identityKeyPairKeys = Curve.generateKeyPair();
IdentityKeyPair ownKey = new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()),
identityKeyPairKeys.getPrivateKey());
return ownKey;
}
private static int generateRegistrationId() {
Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Generating axolotl registration ID...");
int reg_id = KeyHelper.generateRegistrationId(true);
return reg_id;
}
public SQLiteAxolotlStore(Account account, XmppConnectionService service) {
this.account = account;
this.mXmppConnectionService = service;
this.localRegistrationId = loadRegistrationId();
this.currentPreKeyId = loadCurrentPreKeyId();
for (SignedPreKeyRecord record : loadSignedPreKeys()) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got Axolotl signed prekey record:" + record.getId());
}
}
public int getCurrentPreKeyId() {
return currentPreKeyId;
}
// --------------------------------------
// IdentityKeyStore
// --------------------------------------
private IdentityKeyPair loadIdentityKeyPair() {
String ownName = account.getJid().toBareJid().toString();
IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account,
ownName);
if (ownKey != null) {
return ownKey;
} else {
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve axolotl key for account " + ownName);
ownKey = generateIdentityKeyPair();
mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownName, ownKey);
}
return ownKey;
}
private int loadRegistrationId() {
return loadRegistrationId(false);
}
private int loadRegistrationId(boolean regenerate) {
String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID);
int reg_id;
if (!regenerate && regIdString != null) {
reg_id = Integer.valueOf(regIdString);
} else {
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve axolotl registration id for account " + account.getJid());
reg_id = generateRegistrationId();
boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id));
if (success) {
mXmppConnectionService.databaseBackend.updateAccount(account);
} else {
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Failed to write new key to the database!");
}
}
return reg_id;
}
private int loadCurrentPreKeyId() {
String regIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID);
int reg_id;
if (regIdString != null) {
reg_id = Integer.valueOf(regIdString);
} else {
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve current prekey id for account " + account.getJid());
reg_id = 0;
}
return reg_id;
}
public void regenerate() {
mXmppConnectionService.databaseBackend.wipeAxolotlDb(account);
account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0));
identityKeyPair = loadIdentityKeyPair();
localRegistrationId = loadRegistrationId(true);
currentPreKeyId = 0;
mXmppConnectionService.updateAccountUi();
}
/**
* Get the local client's identity key pair.
*
* @return The local client's persistent identity key pair.
*/
@Override
public IdentityKeyPair getIdentityKeyPair() {
if(identityKeyPair == null) {
identityKeyPair = loadIdentityKeyPair();
}
return identityKeyPair;
}
/**
* Return the local client's registration ID.
* <p/>
* Clients should maintain a registration ID, a random number
* between 1 and 16380 that's generated once at install time.
*
* @return the local client's registration ID.
*/
@Override
public int getLocalRegistrationId() {
return localRegistrationId;
}
/**
* Save a remote client's identity key
* <p/>
* Store a remote client's identity key as trusted.
*
* @param name The name of the remote client.
* @param identityKey The remote client's identity key.
*/
@Override
public void saveIdentity(String name, IdentityKey identityKey) {
if(!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) {
mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey);
}
}
/**
* Verify a remote client's identity key.
* <p/>
* Determine whether a remote client's identity is trusted. Convention is
* that the TextSecure protocol is 'trust on first use.' This means that
* an identity key is considered 'trusted' if there is no entry for the recipient
* in the local store, or if it matches the saved key for a recipient in the local
* store. Only if it mismatches an entry in the local store is it considered
* 'untrusted.'
*
* @param name The name of the remote client.
* @param identityKey The identity key to verify.
* @return true if trusted, false if untrusted.
*/
@Override
public boolean isTrustedIdentity(String name, IdentityKey identityKey) {
return true;
}
public Trust getFingerprintTrust(String fingerprint) {
return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint);
}
public void setFingerprintTrust(String fingerprint, Trust trust) {
mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust);
}
public Set<IdentityKey> getContactUndecidedKeys(String bareJid, Trust trust) {
return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust);
}
public long getContactNumTrustedKeys(String bareJid) {
return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid);
}
// --------------------------------------
// SessionStore
// --------------------------------------
/**
* Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple,
* or a new SessionRecord if one does not currently exist.
* <p/>
* It is important that implementations return a copy of the current durable information. The
* returned SessionRecord may be modified, but those changes should not have an effect on the
* durable session state (what is returned by subsequent calls to this method) without the
* store method being called here first.
*
* @param address The name and device ID of the remote client.
* @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or
* a new SessionRecord if one does not currently exist.
*/
@Override
public SessionRecord loadSession(AxolotlAddress address) {
SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address);
return (session != null) ? session : new SessionRecord();
}
/**
* Returns all known devices with active sessions for a recipient
*
* @param name the name of the client.
* @return all known sub-devices with active sessions.
*/
@Override
public List<Integer> getSubDeviceSessions(String name) {
return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account,
new AxolotlAddress(name, 0));
}
/**
* Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple.
*
* @param address the address of the remote client.
* @param record the current SessionRecord for the remote client.
*/
@Override
public void storeSession(AxolotlAddress address, SessionRecord record) {
mXmppConnectionService.databaseBackend.storeSession(account, address, record);
}
/**
* Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple.
*
* @param address the address of the remote client.
* @return true if a {@link SessionRecord} exists, false otherwise.
*/
@Override
public boolean containsSession(AxolotlAddress address) {
return mXmppConnectionService.databaseBackend.containsSession(account, address);
}
/**
* Remove a {@link SessionRecord} for a recipientId + deviceId tuple.
*
* @param address the address of the remote client.
*/
@Override
public void deleteSession(AxolotlAddress address) {
mXmppConnectionService.databaseBackend.deleteSession(account, address);
}
/**
* Remove the {@link SessionRecord}s corresponding to all devices of a recipientId.
*
* @param name the name of the remote client.
*/
@Override
public void deleteAllSessions(String name) {
mXmppConnectionService.databaseBackend.deleteAllSessions(account,
new AxolotlAddress(name, 0));
}
// --------------------------------------
// PreKeyStore
// --------------------------------------
/**
* Load a local PreKeyRecord.
*
* @param preKeyId the ID of the local PreKeyRecord.
* @return the corresponding PreKeyRecord.
* @throws InvalidKeyIdException when there is no corresponding PreKeyRecord.
*/
@Override
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId);
if (record == null) {
throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId);
}
return record;
}
/**
* Store a local PreKeyRecord.
*
* @param preKeyId the ID of the PreKeyRecord to store.
* @param record the PreKeyRecord.
*/
@Override
public void storePreKey(int preKeyId, PreKeyRecord record) {
mXmppConnectionService.databaseBackend.storePreKey(account, record);
currentPreKeyId = preKeyId;
boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId));
if (success) {
mXmppConnectionService.databaseBackend.updateAccount(account);
} else {
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Failed to write new prekey id to the database!");
}
}
/**
* @param preKeyId A PreKeyRecord ID.
* @return true if the store has a record for the preKeyId, otherwise false.
*/
@Override
public boolean containsPreKey(int preKeyId) {
return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId);
}
/**
* Delete a PreKeyRecord from local storage.
*
* @param preKeyId The ID of the PreKeyRecord to remove.
*/
@Override
public void removePreKey(int preKeyId) {
mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId);
}
// --------------------------------------
// SignedPreKeyStore
// --------------------------------------
/**
* Load a local SignedPreKeyRecord.
*
* @param signedPreKeyId the ID of the local SignedPreKeyRecord.
* @return the corresponding SignedPreKeyRecord.
* @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord.
*/
@Override
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId);
if (record == null) {
throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId);
}
return record;
}
/**
* Load all local SignedPreKeyRecords.
*
* @return All stored SignedPreKeyRecords.
*/
@Override
public List<SignedPreKeyRecord> loadSignedPreKeys() {
return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account);
}
/**
* Store a local SignedPreKeyRecord.
*
* @param signedPreKeyId the ID of the SignedPreKeyRecord to store.
* @param record the SignedPreKeyRecord.
*/
@Override
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record);
}
/**
* @param signedPreKeyId A SignedPreKeyRecord ID.
* @return true if the store has a record for the signedPreKeyId, otherwise false.
*/
@Override
public boolean containsSignedPreKey(int signedPreKeyId) {
return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId);
}
/**
* Delete a SignedPreKeyRecord from local storage.
*
* @param signedPreKeyId The ID of the SignedPreKeyRecord to remove.
*/
@Override
public void removeSignedPreKey(int signedPreKeyId) {
mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId);
}
}
public static class XmppAxolotlSession {
private final SessionCipher cipher;
private Integer preKeyId = null;
private final SQLiteAxolotlStore sqLiteAxolotlStore;
private final AxolotlAddress remoteAddress;
private final Account account;
private String fingerprint = null;
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) {
this(account, store, remoteAddress);
this.fingerprint = fingerprint;
}
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
this.cipher = new SessionCipher(store, remoteAddress);
this.remoteAddress = remoteAddress;
this.sqLiteAxolotlStore = store;
this.account = account;
}
public Integer getPreKeyId() {
return preKeyId;
}
public void resetPreKeyId() {
preKeyId = null;
}
public String getFingerprint() {
return fingerprint;
}
protected void setTrust(SQLiteAxolotlStore.Trust trust) {
sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust);
}
protected SQLiteAxolotlStore.Trust getTrust() {
return sqLiteAxolotlStore.getFingerprintTrust(fingerprint);
}
@Nullable
public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) {
byte[] plaintext = null;
SQLiteAxolotlStore.Trust trust = getTrust();
switch (trust) {
case INACTIVE:
case UNDECIDED:
case UNTRUSTED:
case TRUSTED:
try {
try {
PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents());
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", "");
if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) {
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Had session with fingerprint "+ this.fingerprint+", received message with fingerprint "+fingerprint);
} else {
this.fingerprint = fingerprint;
plaintext = cipher.decrypt(message);
if (message.getPreKeyId().isPresent()) {
preKeyId = message.getPreKeyId().get();
}
}
} catch (InvalidMessageException | InvalidVersionException e) {
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"WhisperMessage received");
WhisperMessage message = new WhisperMessage(incomingHeader.getContents());
plaintext = cipher.decrypt(message);
} catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage());
}
} catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage());
}
if (plaintext != null && trust == SQLiteAxolotlStore.Trust.INACTIVE) {
setTrust(SQLiteAxolotlStore.Trust.TRUSTED);
}
break;
case COMPROMISED:
default:
// ignore
break;
}
return plaintext;
}
@Nullable
public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(@NonNull byte[] outgoingMessage) {
SQLiteAxolotlStore.Trust trust = getTrust();
if (trust == SQLiteAxolotlStore.Trust.TRUSTED) {
CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
XmppAxolotlMessage.XmppAxolotlMessageHeader header =
new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(),
ciphertextMessage.serialize());
return header;
} else {
return null;
}
}
}
private static class AxolotlAddressMap<T> { private static class AxolotlAddressMap<T> {
protected Map<String, Map<Integer, T>> map; protected Map<String, Map<Integer, T>> map;
protected final Object MAP_LOCK = new Object(); protected final Object MAP_LOCK = new Object();
@ -741,11 +187,11 @@ public class AxolotlService {
} }
public Set<IdentityKey> getKeysWithTrust(SQLiteAxolotlStore.Trust trust) { public Set<IdentityKey> getKeysWithTrust(SQLiteAxolotlStore.Trust trust) {
return axolotlStore.getContactUndecidedKeys(account.getJid().toBareJid().toString(), trust); return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust);
} }
public Set<IdentityKey> getKeysWithTrust(SQLiteAxolotlStore.Trust trust, Contact contact) { public Set<IdentityKey> getKeysWithTrust(SQLiteAxolotlStore.Trust trust, Contact contact) {
return axolotlStore.getContactUndecidedKeys(contact.getJid().toBareJid().toString(), trust); return axolotlStore.getContactKeysWithTrust(contact.getJid().toBareJid().toString(), trust);
} }
public long getNumTrustedKeys(Contact contact) { public long getNumTrustedKeys(Contact contact) {
@ -782,7 +228,7 @@ public class AxolotlService {
} }
public int getOwnDeviceId() { public int getOwnDeviceId() {
return axolotlStore.loadRegistrationId(); return axolotlStore.getLocalRegistrationId();
} }
public Set<Integer> getOwnDeviceIds() { public Set<Integer> getOwnDeviceIds() {
@ -1139,7 +585,7 @@ public class AxolotlService {
} }
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign headers..."); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign headers...");
for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) { for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) {
Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.remoteAddress.toString()); Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString());
//if(!session.isTrusted()) { //if(!session.isTrusted()) {
// TODO: handle this properly // TODO: handle this properly
// continue; // continue;
@ -1148,7 +594,7 @@ public class AxolotlService {
} }
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own headers..."); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own headers...");
for (XmppAxolotlSession session : findOwnSessions()) { for (XmppAxolotlSession session : findOwnSessions()) {
Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.remoteAddress.toString()); Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString());
// if(!session.isTrusted()) { // if(!session.isTrusted()) {
// TODO: handle this properly // TODO: handle this properly
// continue; // continue;

View File

@ -0,0 +1,473 @@
package eu.siacs.conversations.crypto.axolotl;
import android.util.Log;
import android.util.LruCache;
import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import org.whispersystems.libaxolotl.util.KeyHelper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.XmppConnectionService;
public class SQLiteAxolotlStore implements AxolotlStore {
public static final String PREKEY_TABLENAME = "prekeys";
public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys";
public static final String SESSION_TABLENAME = "sessions";
public static final String IDENTITIES_TABLENAME = "identities";
public static final String ACCOUNT = "account";
public static final String DEVICE_ID = "device_id";
public static final String ID = "id";
public static final String KEY = "key";
public static final String FINGERPRINT = "fingerprint";
public static final String NAME = "name";
public static final String TRUSTED = "trusted";
public static final String OWN = "ownkey";
public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id";
public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id";
private static final int NUM_TRUSTS_TO_CACHE = 100;
private final Account account;
private final XmppConnectionService mXmppConnectionService;
private IdentityKeyPair identityKeyPair;
private int localRegistrationId;
private int currentPreKeyId = 0;
private final LruCache<String, Trust> trustCache =
new LruCache<String, Trust>(NUM_TRUSTS_TO_CACHE) {
@Override
protected Trust create(String fingerprint) {
return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint);
}
};
public enum Trust {
UNDECIDED(0),
TRUSTED(1),
UNTRUSTED(2),
COMPROMISED(3),
INACTIVE(4);
private static final Map<Integer, Trust> trustsByValue = new HashMap<>();
static {
for (Trust trust : Trust.values()) {
trustsByValue.put(trust.getCode(), trust);
}
}
private final int code;
Trust(int code) {
this.code = code;
}
public int getCode() {
return this.code;
}
public String toString() {
switch (this) {
case UNDECIDED:
return "Trust undecided " + getCode();
case TRUSTED:
return "Trusted " + getCode();
case COMPROMISED:
return "Compromised " + getCode();
case INACTIVE:
return "Inactive " + getCode();
case UNTRUSTED:
default:
return "Untrusted " + getCode();
}
}
public static Trust fromBoolean(Boolean trusted) {
return trusted ? TRUSTED : UNTRUSTED;
}
public static Trust fromCode(int code) {
return trustsByValue.get(code);
}
}
private static IdentityKeyPair generateIdentityKeyPair() {
Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl IdentityKeyPair...");
ECKeyPair identityKeyPairKeys = Curve.generateKeyPair();
return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()),
identityKeyPairKeys.getPrivateKey());
}
private static int generateRegistrationId() {
Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl registration ID...");
return KeyHelper.generateRegistrationId(true);
}
public SQLiteAxolotlStore(Account account, XmppConnectionService service) {
this.account = account;
this.mXmppConnectionService = service;
this.localRegistrationId = loadRegistrationId();
this.currentPreKeyId = loadCurrentPreKeyId();
for (SignedPreKeyRecord record : loadSignedPreKeys()) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId());
}
}
public int getCurrentPreKeyId() {
return currentPreKeyId;
}
// --------------------------------------
// IdentityKeyStore
// --------------------------------------
private IdentityKeyPair loadIdentityKeyPair() {
String ownName = account.getJid().toBareJid().toString();
IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account,
ownName);
if (ownKey != null) {
return ownKey;
} else {
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl key for account " + ownName);
ownKey = generateIdentityKeyPair();
mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownName, ownKey);
}
return ownKey;
}
private int loadRegistrationId() {
return loadRegistrationId(false);
}
private int loadRegistrationId(boolean regenerate) {
String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID);
int reg_id;
if (!regenerate && regIdString != null) {
reg_id = Integer.valueOf(regIdString);
} else {
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid());
reg_id = generateRegistrationId();
boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id));
if (success) {
mXmppConnectionService.databaseBackend.updateAccount(account);
} else {
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new key to the database!");
}
}
return reg_id;
}
private int loadCurrentPreKeyId() {
String regIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID);
int reg_id;
if (regIdString != null) {
reg_id = Integer.valueOf(regIdString);
} else {
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid());
reg_id = 0;
}
return reg_id;
}
public void regenerate() {
mXmppConnectionService.databaseBackend.wipeAxolotlDb(account);
trustCache.evictAll();
account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0));
identityKeyPair = loadIdentityKeyPair();
localRegistrationId = loadRegistrationId(true);
currentPreKeyId = 0;
mXmppConnectionService.updateAccountUi();
}
/**
* Get the local client's identity key pair.
*
* @return The local client's persistent identity key pair.
*/
@Override
public IdentityKeyPair getIdentityKeyPair() {
if (identityKeyPair == null) {
identityKeyPair = loadIdentityKeyPair();
}
return identityKeyPair;
}
/**
* Return the local client's registration ID.
* <p/>
* Clients should maintain a registration ID, a random number
* between 1 and 16380 that's generated once at install time.
*
* @return the local client's registration ID.
*/
@Override
public int getLocalRegistrationId() {
return localRegistrationId;
}
/**
* Save a remote client's identity key
* <p/>
* Store a remote client's identity key as trusted.
*
* @param name The name of the remote client.
* @param identityKey The remote client's identity key.
*/
@Override
public void saveIdentity(String name, IdentityKey identityKey) {
if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) {
mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey);
}
}
/**
* Verify a remote client's identity key.
* <p/>
* Determine whether a remote client's identity is trusted. Convention is
* that the TextSecure protocol is 'trust on first use.' This means that
* an identity key is considered 'trusted' if there is no entry for the recipient
* in the local store, or if it matches the saved key for a recipient in the local
* store. Only if it mismatches an entry in the local store is it considered
* 'untrusted.'
*
* @param name The name of the remote client.
* @param identityKey The identity key to verify.
* @return true if trusted, false if untrusted.
*/
@Override
public boolean isTrustedIdentity(String name, IdentityKey identityKey) {
return true;
}
public Trust getFingerprintTrust(String fingerprint) {
return (fingerprint == null)? null : trustCache.get(fingerprint);
}
public void setFingerprintTrust(String fingerprint, Trust trust) {
mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust);
trustCache.remove(fingerprint);
}
public Set<IdentityKey> getContactKeysWithTrust(String bareJid, Trust trust) {
return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust);
}
public long getContactNumTrustedKeys(String bareJid) {
return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid);
}
// --------------------------------------
// SessionStore
// --------------------------------------
/**
* Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple,
* or a new SessionRecord if one does not currently exist.
* <p/>
* It is important that implementations return a copy of the current durable information. The
* returned SessionRecord may be modified, but those changes should not have an effect on the
* durable session state (what is returned by subsequent calls to this method) without the
* store method being called here first.
*
* @param address The name and device ID of the remote client.
* @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or
* a new SessionRecord if one does not currently exist.
*/
@Override
public SessionRecord loadSession(AxolotlAddress address) {
SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address);
return (session != null) ? session : new SessionRecord();
}
/**
* Returns all known devices with active sessions for a recipient
*
* @param name the name of the client.
* @return all known sub-devices with active sessions.
*/
@Override
public List<Integer> getSubDeviceSessions(String name) {
return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account,
new AxolotlAddress(name, 0));
}
/**
* Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple.
*
* @param address the address of the remote client.
* @param record the current SessionRecord for the remote client.
*/
@Override
public void storeSession(AxolotlAddress address, SessionRecord record) {
mXmppConnectionService.databaseBackend.storeSession(account, address, record);
}
/**
* Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple.
*
* @param address the address of the remote client.
* @return true if a {@link SessionRecord} exists, false otherwise.
*/
@Override
public boolean containsSession(AxolotlAddress address) {
return mXmppConnectionService.databaseBackend.containsSession(account, address);
}
/**
* Remove a {@link SessionRecord} for a recipientId + deviceId tuple.
*
* @param address the address of the remote client.
*/
@Override
public void deleteSession(AxolotlAddress address) {
mXmppConnectionService.databaseBackend.deleteSession(account, address);
}
/**
* Remove the {@link SessionRecord}s corresponding to all devices of a recipientId.
*
* @param name the name of the remote client.
*/
@Override
public void deleteAllSessions(String name) {
AxolotlAddress address = new AxolotlAddress(name, 0);
mXmppConnectionService.databaseBackend.deleteAllSessions(account,
address);
}
// --------------------------------------
// PreKeyStore
// --------------------------------------
/**
* Load a local PreKeyRecord.
*
* @param preKeyId the ID of the local PreKeyRecord.
* @return the corresponding PreKeyRecord.
* @throws InvalidKeyIdException when there is no corresponding PreKeyRecord.
*/
@Override
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId);
if (record == null) {
throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId);
}
return record;
}
/**
* Store a local PreKeyRecord.
*
* @param preKeyId the ID of the PreKeyRecord to store.
* @param record the PreKeyRecord.
*/
@Override
public void storePreKey(int preKeyId, PreKeyRecord record) {
mXmppConnectionService.databaseBackend.storePreKey(account, record);
currentPreKeyId = preKeyId;
boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId));
if (success) {
mXmppConnectionService.databaseBackend.updateAccount(account);
} else {
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new prekey id to the database!");
}
}
/**
* @param preKeyId A PreKeyRecord ID.
* @return true if the store has a record for the preKeyId, otherwise false.
*/
@Override
public boolean containsPreKey(int preKeyId) {
return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId);
}
/**
* Delete a PreKeyRecord from local storage.
*
* @param preKeyId The ID of the PreKeyRecord to remove.
*/
@Override
public void removePreKey(int preKeyId) {
mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId);
}
// --------------------------------------
// SignedPreKeyStore
// --------------------------------------
/**
* Load a local SignedPreKeyRecord.
*
* @param signedPreKeyId the ID of the local SignedPreKeyRecord.
* @return the corresponding SignedPreKeyRecord.
* @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord.
*/
@Override
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId);
if (record == null) {
throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId);
}
return record;
}
/**
* Load all local SignedPreKeyRecords.
*
* @return All stored SignedPreKeyRecords.
*/
@Override
public List<SignedPreKeyRecord> loadSignedPreKeys() {
return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account);
}
/**
* Store a local SignedPreKeyRecord.
*
* @param signedPreKeyId the ID of the SignedPreKeyRecord to store.
* @param record the SignedPreKeyRecord.
*/
@Override
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record);
}
/**
* @param signedPreKeyId A SignedPreKeyRecord ID.
* @return true if the store has a record for the signedPreKeyId, otherwise false.
*/
@Override
public boolean containsSignedPreKey(int signedPreKeyId) {
return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId);
}
/**
* Delete a SignedPreKeyRecord from local storage.
*
* @param signedPreKeyId The ID of the SignedPreKeyRecord to remove.
*/
@Override
public void removeSignedPreKey(int signedPreKeyId) {
mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId);
}
}

View File

@ -67,11 +67,11 @@ public class XmppAxolotlMessage {
} }
public static class XmppAxolotlPlaintextMessage { public static class XmppAxolotlPlaintextMessage {
private final AxolotlService.XmppAxolotlSession session; private final XmppAxolotlSession session;
private final String plaintext; private final String plaintext;
private final String fingerprint; private final String fingerprint;
public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext, String fingerprint) { public XmppAxolotlPlaintextMessage(XmppAxolotlSession session, String plaintext, String fingerprint) {
this.session = session; this.session = session;
this.plaintext = plaintext; this.plaintext = plaintext;
this.fingerprint = fingerprint; this.fingerprint = fingerprint;
@ -81,7 +81,7 @@ public class XmppAxolotlMessage {
return plaintext; return plaintext;
} }
public AxolotlService.XmppAxolotlSession getSession() { public XmppAxolotlSession getSession() {
return session; return session;
} }
@ -180,7 +180,7 @@ public class XmppAxolotlMessage {
} }
public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) throws CryptoFailedException { public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, byte[] key, String fingerprint) throws CryptoFailedException {
XmppAxolotlPlaintextMessage plaintextMessage = null; XmppAxolotlPlaintextMessage plaintextMessage = null;
try { try {

View File

@ -0,0 +1,131 @@
package eu.siacs.conversations.crypto.axolotl;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
public class XmppAxolotlSession {
private final SessionCipher cipher;
private Integer preKeyId = null;
private final SQLiteAxolotlStore sqLiteAxolotlStore;
public AxolotlAddress getRemoteAddress() {
return remoteAddress;
}
private final AxolotlAddress remoteAddress;
private final Account account;
private String fingerprint = null;
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) {
this(account, store, remoteAddress);
this.fingerprint = fingerprint;
}
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
this.cipher = new SessionCipher(store, remoteAddress);
this.remoteAddress = remoteAddress;
this.sqLiteAxolotlStore = store;
this.account = account;
}
public Integer getPreKeyId() {
return preKeyId;
}
public void resetPreKeyId() {
preKeyId = null;
}
public String getFingerprint() {
return fingerprint;
}
protected void setTrust(SQLiteAxolotlStore.Trust trust) {
sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust);
}
protected SQLiteAxolotlStore.Trust getTrust() {
return sqLiteAxolotlStore.getFingerprintTrust(fingerprint);
}
@Nullable
public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) {
byte[] plaintext = null;
SQLiteAxolotlStore.Trust trust = getTrust();
switch (trust) {
case INACTIVE:
case UNDECIDED:
case UNTRUSTED:
case TRUSTED:
try {
try {
PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents());
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", "");
if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) {
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.fingerprint + ", received message with fingerprint " + fingerprint);
} else {
this.fingerprint = fingerprint;
plaintext = cipher.decrypt(message);
if (message.getPreKeyId().isPresent()) {
preKeyId = message.getPreKeyId().get();
}
}
} catch (InvalidMessageException | InvalidVersionException e) {
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received");
WhisperMessage message = new WhisperMessage(incomingHeader.getContents());
plaintext = cipher.decrypt(message);
} catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
}
} catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
}
if (plaintext != null && trust == SQLiteAxolotlStore.Trust.INACTIVE) {
setTrust(SQLiteAxolotlStore.Trust.TRUSTED);
}
break;
case COMPROMISED:
default:
// ignore
break;
}
return plaintext;
}
@Nullable
public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(@NonNull byte[] outgoingMessage) {
SQLiteAxolotlStore.Trust trust = getTrust();
if (trust == SQLiteAxolotlStore.Trust.TRUSTED) {
CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
XmppAxolotlMessage.XmppAxolotlMessageHeader header =
new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(),
ciphertextMessage.serialize());
return header;
} else {
return null;
}
}
}

View File

@ -8,7 +8,7 @@ import java.net.URL;
import java.util.Arrays; import java.util.Arrays;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.MimeUtils;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
@ -689,6 +689,6 @@ public class Message extends AbstractEntity {
public boolean isTrusted() { public boolean isTrusted() {
return conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint) return conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint)
== AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED; == SQLiteAxolotlStore.Trust.TRUSTED;
} }
} }

View File

@ -27,6 +27,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
@ -55,56 +56,56 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ Contact.JID + ") ON CONFLICT REPLACE);"; + Contact.JID + ") ON CONFLICT REPLACE);";
private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE " private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
+ AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME + "(" + SQLiteAxolotlStore.PREKEY_TABLENAME + "("
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, " + SQLiteAxolotlStore.ID + " INTEGER, "
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + SQLiteAxolotlStore.ACCOUNT
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+ AxolotlService.SQLiteAxolotlStore.ID + SQLiteAxolotlStore.ID
+ ") ON CONFLICT REPLACE" + ") ON CONFLICT REPLACE"
+");"; +");";
private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE " private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE "
+ AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "(" + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, " + SQLiteAxolotlStore.ID + " INTEGER, "
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + SQLiteAxolotlStore.ACCOUNT
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+ AxolotlService.SQLiteAxolotlStore.ID + SQLiteAxolotlStore.ID
+ ") ON CONFLICT REPLACE"+ + ") ON CONFLICT REPLACE"+
");"; ");";
private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE " private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE "
+ AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME + "(" + SQLiteAxolotlStore.SESSION_TABLENAME + "("
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, " + SQLiteAxolotlStore.NAME + " TEXT, "
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " INTEGER, " + SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + SQLiteAxolotlStore.ACCOUNT
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+ AxolotlService.SQLiteAxolotlStore.NAME + ", " + SQLiteAxolotlStore.NAME + ", "
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + SQLiteAxolotlStore.DEVICE_ID
+ ") ON CONFLICT REPLACE" + ") ON CONFLICT REPLACE"
+");"; +");";
private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE " private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE "
+ AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME + "(" + SQLiteAxolotlStore.IDENTITIES_TABLENAME + "("
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, " + SQLiteAxolotlStore.NAME + " TEXT, "
+ AxolotlService.SQLiteAxolotlStore.OWN + " INTEGER, " + SQLiteAxolotlStore.OWN + " INTEGER, "
+ AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " TEXT, " + SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
+ AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, " + SQLiteAxolotlStore.TRUSTED + " INTEGER, "
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + SQLiteAxolotlStore.ACCOUNT
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+ AxolotlService.SQLiteAxolotlStore.NAME + ", " + SQLiteAxolotlStore.NAME + ", "
+ AxolotlService.SQLiteAxolotlStore.FINGERPRINT + SQLiteAxolotlStore.FINGERPRINT
+ ") ON CONFLICT IGNORE" + ") ON CONFLICT IGNORE"
+");"; +");";
@ -567,11 +568,11 @@ public class DatabaseBackend extends SQLiteOpenHelper {
String[] selectionArgs = {account.getUuid(), String[] selectionArgs = {account.getUuid(),
contact.getName(), contact.getName(),
Integer.toString(contact.getDeviceId())}; Integer.toString(contact.getDeviceId())};
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
columns, columns,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND " + SQLiteAxolotlStore.NAME + " = ? AND "
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ", + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
selectionArgs, selectionArgs,
null, null, null); null, null, null);
@ -584,7 +585,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
if(cursor.getCount() != 0) { if(cursor.getCount() != 0) {
cursor.moveToFirst(); cursor.moveToFirst();
try { try {
session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
} catch (IOException e) { } catch (IOException e) {
cursor.close(); cursor.close();
throw new AssertionError(e); throw new AssertionError(e);
@ -597,19 +598,19 @@ 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<>(); List<Integer> devices = new ArrayList<>();
final SQLiteDatabase db = this.getReadableDatabase(); final SQLiteDatabase db = this.getReadableDatabase();
String[] columns = {AxolotlService.SQLiteAxolotlStore.DEVICE_ID}; String[] columns = {SQLiteAxolotlStore.DEVICE_ID};
String[] selectionArgs = {account.getUuid(), String[] selectionArgs = {account.getUuid(),
contact.getName()}; contact.getName()};
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
columns, columns,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ?", + SQLiteAxolotlStore.NAME + " = ?",
selectionArgs, selectionArgs,
null, null, null); null, null, null);
while(cursor.moveToNext()) { while(cursor.moveToNext()) {
devices.add(cursor.getInt( devices.add(cursor.getInt(
cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.DEVICE_ID))); cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID)));
} }
cursor.close(); cursor.close();
@ -626,11 +627,11 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) { public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) {
SQLiteDatabase db = this.getWritableDatabase(); SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(AxolotlService.SQLiteAxolotlStore.NAME, contact.getName()); values.put(SQLiteAxolotlStore.NAME, contact.getName());
values.put(AxolotlService.SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId()); values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(),Base64.DEFAULT)); values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(),Base64.DEFAULT));
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
db.insert(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, null, values); db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
} }
public void deleteSession(Account account, AxolotlAddress contact) { public void deleteSession(Account account, AxolotlAddress contact) {
@ -638,30 +639,30 @@ public class DatabaseBackend extends SQLiteOpenHelper {
String[] args = {account.getUuid(), String[] args = {account.getUuid(),
contact.getName(), contact.getName(),
Integer.toString(contact.getDeviceId())}; Integer.toString(contact.getDeviceId())};
db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND " + SQLiteAxolotlStore.NAME + " = ? AND "
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ", + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
args); args);
} }
public void deleteAllSessions(Account account, AxolotlAddress contact) { public void deleteAllSessions(Account account, AxolotlAddress contact) {
SQLiteDatabase db = this.getWritableDatabase(); SQLiteDatabase db = this.getWritableDatabase();
String[] args = {account.getUuid(), contact.getName()}; String[] args = {account.getUuid(), contact.getName()};
db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " SQLiteAxolotlStore.ACCOUNT + "=? AND "
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ?", + SQLiteAxolotlStore.NAME + " = ?",
args); args);
} }
private Cursor getCursorForPreKey(Account account, int preKeyId) { private Cursor getCursorForPreKey(Account account, int preKeyId) {
SQLiteDatabase db = this.getReadableDatabase(); SQLiteDatabase db = this.getReadableDatabase();
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; String[] columns = {SQLiteAxolotlStore.KEY};
String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)}; String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME,
columns, columns,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " SQLiteAxolotlStore.ACCOUNT + "=? AND "
+ AxolotlService.SQLiteAxolotlStore.ID + "=?", + SQLiteAxolotlStore.ID + "=?",
selectionArgs, selectionArgs,
null, null, null); null, null, null);
@ -674,7 +675,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
if(cursor.getCount() != 0) { if(cursor.getCount() != 0) {
cursor.moveToFirst(); cursor.moveToFirst();
try { try {
record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
} catch (IOException e ) { } catch (IOException e ) {
throw new AssertionError(e); throw new AssertionError(e);
} }
@ -693,28 +694,28 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void storePreKey(Account account, PreKeyRecord record) { public void storePreKey(Account account, PreKeyRecord record) {
SQLiteDatabase db = this.getWritableDatabase(); SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId()); values.put(SQLiteAxolotlStore.ID, record.getId());
values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT)); values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
db.insert(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, null, values); db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
} }
public void deletePreKey(Account account, int preKeyId) { public void deletePreKey(Account account, int preKeyId) {
SQLiteDatabase db = this.getWritableDatabase(); SQLiteDatabase db = this.getWritableDatabase();
String[] args = {account.getUuid(), Integer.toString(preKeyId)}; String[] args = {account.getUuid(), Integer.toString(preKeyId)};
db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " SQLiteAxolotlStore.ACCOUNT + "=? AND "
+ AxolotlService.SQLiteAxolotlStore.ID + "=?", + SQLiteAxolotlStore.ID + "=?",
args); args);
} }
private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) { private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
SQLiteDatabase db = this.getReadableDatabase(); SQLiteDatabase db = this.getReadableDatabase();
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; String[] columns = {SQLiteAxolotlStore.KEY};
String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)}; String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)};
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
columns, columns,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " + AxolotlService.SQLiteAxolotlStore.ID + "=?", SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?",
selectionArgs, selectionArgs,
null, null, null); null, null, null);
@ -727,7 +728,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
if(cursor.getCount() != 0) { if(cursor.getCount() != 0) {
cursor.moveToFirst(); cursor.moveToFirst();
try { try {
record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
} catch (IOException e ) { } catch (IOException e ) {
throw new AssertionError(e); throw new AssertionError(e);
} }
@ -739,17 +740,17 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) { public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
List<SignedPreKeyRecord> prekeys = new ArrayList<>(); List<SignedPreKeyRecord> prekeys = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase(); SQLiteDatabase db = this.getReadableDatabase();
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; String[] columns = {SQLiteAxolotlStore.KEY};
String[] selectionArgs = {account.getUuid()}; String[] selectionArgs = {account.getUuid()};
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
columns, columns,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=?", SQLiteAxolotlStore.ACCOUNT + "=?",
selectionArgs, selectionArgs,
null, null, null); null, null, null);
while(cursor.moveToNext()) { while(cursor.moveToNext()) {
try { try {
prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)), Base64.DEFAULT))); prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)));
} catch (IOException ignored) { } catch (IOException ignored) {
} }
} }
@ -767,18 +768,18 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void storeSignedPreKey(Account account, SignedPreKeyRecord record) { public void storeSignedPreKey(Account account, SignedPreKeyRecord record) {
SQLiteDatabase db = this.getWritableDatabase(); SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId()); values.put(SQLiteAxolotlStore.ID, record.getId());
values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT)); values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
db.insert(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values); db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
} }
public void deleteSignedPreKey(Account account, int signedPreKeyId) { public void deleteSignedPreKey(Account account, int signedPreKeyId) {
SQLiteDatabase db = this.getWritableDatabase(); SQLiteDatabase db = this.getWritableDatabase();
String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)}; String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)};
db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " SQLiteAxolotlStore.ACCOUNT + "=? AND "
+ AxolotlService.SQLiteAxolotlStore.ID + "=?", + SQLiteAxolotlStore.ID + "=?",
args); args);
} }
@ -792,24 +793,24 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private Cursor getIdentityKeyCursor(Account account, String name, Boolean own, String fingerprint) { private Cursor getIdentityKeyCursor(Account account, String name, Boolean own, String fingerprint) {
final SQLiteDatabase db = this.getReadableDatabase(); final SQLiteDatabase db = this.getReadableDatabase();
String[] columns = {AxolotlService.SQLiteAxolotlStore.TRUSTED, String[] columns = {SQLiteAxolotlStore.TRUSTED,
AxolotlService.SQLiteAxolotlStore.KEY}; SQLiteAxolotlStore.KEY};
ArrayList<String> selectionArgs = new ArrayList<>(4); ArrayList<String> selectionArgs = new ArrayList<>(4);
selectionArgs.add(account.getUuid()); selectionArgs.add(account.getUuid());
String selectionString = AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?"; String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?";
if (name != null){ if (name != null){
selectionArgs.add(name); selectionArgs.add(name);
selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.NAME + " = ?"; selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?";
} }
if (fingerprint != null){ if (fingerprint != null){
selectionArgs.add(fingerprint); selectionArgs.add(fingerprint);
selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ?"; selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?";
} }
if (own != null){ if (own != null){
selectionArgs.add(own?"1":"0"); selectionArgs.add(own?"1":"0");
selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.OWN + " = ?"; selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?";
} }
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
columns, columns,
selectionString, selectionString,
selectionArgs.toArray(new String[selectionArgs.size()]), selectionArgs.toArray(new String[selectionArgs.size()]),
@ -824,7 +825,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
if(cursor.getCount() != 0) { if(cursor.getCount() != 0) {
cursor.moveToFirst(); cursor.moveToFirst();
try { try {
identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
} }
@ -838,18 +839,18 @@ public class DatabaseBackend extends SQLiteOpenHelper {
return loadIdentityKeys(account, name, null); return loadIdentityKeys(account, name, null);
} }
public Set<IdentityKey> loadIdentityKeys(Account account, String name, AxolotlService.SQLiteAxolotlStore.Trust trust) { public Set<IdentityKey> loadIdentityKeys(Account account, String name, SQLiteAxolotlStore.Trust trust) {
Set<IdentityKey> identityKeys = new HashSet<>(); Set<IdentityKey> identityKeys = new HashSet<>();
Cursor cursor = getIdentityKeyCursor(account, name, false); Cursor cursor = getIdentityKeyCursor(account, name, false);
while(cursor.moveToNext()) { while(cursor.moveToNext()) {
if ( trust != null && if ( trust != null &&
cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED)) cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED))
!= trust.getCode()) { != trust.getCode()) {
continue; continue;
} }
try { try {
identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0)); identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0));
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account"+account.getJid().toBareJid()+", address: "+name); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account"+account.getJid().toBareJid()+", address: "+name);
} }
@ -864,55 +865,55 @@ public class DatabaseBackend extends SQLiteOpenHelper {
String[] args = { String[] args = {
account.getUuid(), account.getUuid(),
name, name,
String.valueOf(AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED.getCode()) String.valueOf(SQLiteAxolotlStore.Trust.TRUSTED.getCode())
}; };
return DatabaseUtils.queryNumEntries(db, AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?" SQLiteAxolotlStore.ACCOUNT + " = ?"
+ " AND " + AxolotlService.SQLiteAxolotlStore.NAME + " = ?" + " AND " + SQLiteAxolotlStore.NAME + " = ?"
+ " AND " + AxolotlService.SQLiteAxolotlStore.TRUSTED + " = ?", + " AND " + SQLiteAxolotlStore.TRUSTED + " = ?",
args args
); );
} }
private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) { private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) {
storeIdentityKey(account, name, own, fingerprint, base64Serialized, AxolotlService.SQLiteAxolotlStore.Trust.UNDECIDED); storeIdentityKey(account, name, own, fingerprint, base64Serialized, SQLiteAxolotlStore.Trust.UNDECIDED);
} }
private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, AxolotlService.SQLiteAxolotlStore.Trust trusted) { private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, SQLiteAxolotlStore.Trust trusted) {
SQLiteDatabase db = this.getWritableDatabase(); SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
values.put(AxolotlService.SQLiteAxolotlStore.NAME, name); values.put(SQLiteAxolotlStore.NAME, name);
values.put(AxolotlService.SQLiteAxolotlStore.OWN, own ? 1 : 0); values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0);
values.put(AxolotlService.SQLiteAxolotlStore.FINGERPRINT, fingerprint); values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
values.put(AxolotlService.SQLiteAxolotlStore.KEY, base64Serialized); values.put(SQLiteAxolotlStore.KEY, base64Serialized);
values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trusted.getCode()); values.put(SQLiteAxolotlStore.TRUSTED, trusted.getCode());
db.insert(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
} }
public AxolotlService.SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String fingerprint) { public SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String fingerprint) {
Cursor cursor = getIdentityKeyCursor(account, fingerprint); Cursor cursor = getIdentityKeyCursor(account, fingerprint);
AxolotlService.SQLiteAxolotlStore.Trust trust = null; SQLiteAxolotlStore.Trust trust = null;
if (cursor.getCount() > 0) { if (cursor.getCount() > 0) {
cursor.moveToFirst(); cursor.moveToFirst();
int trustValue = cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED)); int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED));
trust = AxolotlService.SQLiteAxolotlStore.Trust.fromCode(trustValue); trust = SQLiteAxolotlStore.Trust.fromCode(trustValue);
} }
cursor.close(); cursor.close();
return trust; return trust;
} }
public boolean setIdentityKeyTrust(Account account, String fingerprint, AxolotlService.SQLiteAxolotlStore.Trust trust) { public boolean setIdentityKeyTrust(Account account, String fingerprint, SQLiteAxolotlStore.Trust trust) {
SQLiteDatabase db = this.getWritableDatabase(); SQLiteDatabase db = this.getWritableDatabase();
String[] selectionArgs = { String[] selectionArgs = {
account.getUuid(), account.getUuid(),
fingerprint fingerprint
}; };
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trust.getCode()); values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode());
int rows = db.update(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ", + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
selectionArgs); selectionArgs);
return rows == 1; return rows == 1;
} }
@ -922,7 +923,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
} }
public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) { public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) {
storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED); storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), SQLiteAxolotlStore.Trust.TRUSTED);
} }
public void recreateAxolotlDb() { public void recreateAxolotlDb() {
@ -931,13 +932,13 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void recreateAxolotlDb(SQLiteDatabase db) { public void recreateAxolotlDb(SQLiteDatabase db) {
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+">>> (RE)CREATING AXOLOTL DATABASE <<<"); Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+">>> (RE)CREATING AXOLOTL DATABASE <<<");
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME); db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME);
db.execSQL(CREATE_SESSIONS_STATEMENT); db.execSQL(CREATE_SESSIONS_STATEMENT);
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME); db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME);
db.execSQL(CREATE_PREKEYS_STATEMENT); db.execSQL(CREATE_PREKEYS_STATEMENT);
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME); db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME);
db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME); db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME);
db.execSQL(CREATE_IDENTITIES_STATEMENT); db.execSQL(CREATE_IDENTITIES_STATEMENT);
} }
@ -948,17 +949,17 @@ public class DatabaseBackend extends SQLiteOpenHelper {
String[] deleteArgs= { String[] deleteArgs= {
accountName accountName
}; };
db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", SQLiteAxolotlStore.ACCOUNT + " = ?",
deleteArgs); deleteArgs);
db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", SQLiteAxolotlStore.ACCOUNT + " = ?",
deleteArgs); deleteArgs);
db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", SQLiteAxolotlStore.ACCOUNT + " = ?",
deleteArgs); deleteArgs);
db.delete(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", SQLiteAxolotlStore.ACCOUNT + " = ?",
deleteArgs); deleteArgs);
} }
} }

View File

@ -38,7 +38,7 @@ import de.timroes.android.listview.EnhancedListView;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.Trust; import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore.Trust;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Blockable;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;

View File

@ -16,7 +16,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.Trust; import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore.Trust;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;

View File

@ -70,7 +70,7 @@ import java.util.concurrent.RejectedExecutionException;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
@ -610,16 +610,16 @@ public abstract class XmppActivity extends Activity {
protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) { protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) {
final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", "");
final AxolotlService.SQLiteAxolotlStore.Trust trust = account.getAxolotlService() final SQLiteAxolotlStore.Trust trust = account.getAxolotlService()
.getFingerprintTrust(fingerprint); .getFingerprintTrust(fingerprint);
return addFingerprintRowWithListeners(keys, account, identityKey, trust, true, return addFingerprintRowWithListeners(keys, account, identityKey, trust, true,
new CompoundButton.OnCheckedChangeListener() { new CompoundButton.OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked != (trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED)) { if (isChecked != (trust == SQLiteAxolotlStore.Trust.TRUSTED)) {
account.getAxolotlService().setFingerprintTrust(fingerprint, account.getAxolotlService().setFingerprintTrust(fingerprint,
(isChecked) ? AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED : (isChecked) ? SQLiteAxolotlStore.Trust.TRUSTED :
AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED); SQLiteAxolotlStore.Trust.UNTRUSTED);
} }
refreshUi(); refreshUi();
xmppConnectionService.updateAccountUi(); xmppConnectionService.updateAccountUi();
@ -630,7 +630,7 @@ public abstract class XmppActivity extends Activity {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
account.getAxolotlService().setFingerprintTrust(fingerprint, account.getAxolotlService().setFingerprintTrust(fingerprint,
AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED); SQLiteAxolotlStore.Trust.UNTRUSTED);
refreshUi(); refreshUi();
xmppConnectionService.updateAccountUi(); xmppConnectionService.updateAccountUi();
xmppConnectionService.updateConversationUi(); xmppConnectionService.updateConversationUi();
@ -642,12 +642,12 @@ public abstract class XmppActivity extends Activity {
protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account, protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
final IdentityKey identityKey, final IdentityKey identityKey,
AxolotlService.SQLiteAxolotlStore.Trust trust, SQLiteAxolotlStore.Trust trust,
boolean showTag, boolean showTag,
CompoundButton.OnCheckedChangeListener CompoundButton.OnCheckedChangeListener
onCheckedChangeListener, onCheckedChangeListener,
View.OnClickListener onClickListener) { View.OnClickListener onClickListener) {
if (trust == AxolotlService.SQLiteAxolotlStore.Trust.COMPROMISED) { if (trust == SQLiteAxolotlStore.Trust.COMPROMISED) {
return false; return false;
} }
View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false); View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
@ -668,7 +668,7 @@ public abstract class XmppActivity extends Activity {
switch (trust) { switch (trust) {
case UNTRUSTED: case UNTRUSTED:
case TRUSTED: case TRUSTED:
trustToggle.setChecked(trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED, false); trustToggle.setChecked(trust == SQLiteAxolotlStore.Trust.TRUSTED, false);
trustToggle.setEnabled(true); trustToggle.setEnabled(true);
key.setTextColor(getPrimaryTextColor()); key.setTextColor(getPrimaryTextColor());
keyType.setTextColor(getSecondaryTextColor()); keyType.setTextColor(getSecondaryTextColor());

View File

@ -27,7 +27,7 @@ import android.widget.Toast;
import java.util.List; import java.util.List;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
@ -173,11 +173,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else { } else {
viewHolder.indicator.setVisibility(View.VISIBLE); viewHolder.indicator.setVisibility(View.VISIBLE);
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
AxolotlService.SQLiteAxolotlStore.Trust trust = message.getConversation() SQLiteAxolotlStore.Trust trust = message.getConversation()
.getAccount().getAxolotlService().getFingerprintTrust( .getAccount().getAxolotlService().getFingerprintTrust(
message.getAxolotlFingerprint()); message.getAxolotlFingerprint());
if(trust == null || trust != AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED) { if(trust == null || trust != SQLiteAxolotlStore.Trust.TRUSTED) {
viewHolder.indicator.setColorFilter(Color.RED); viewHolder.indicator.setColorFilter(Color.RED);
} else { } else {
viewHolder.indicator.clearColorFilter(); viewHolder.indicator.clearColorFilter();