aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/eu
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/eu')
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java879
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java5
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java421
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java242
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java196
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Conversation.java63
-rw-r--r--src/main/java/eu/siacs/conversations/entities/DownloadableFile.java129
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Message.java91
-rw-r--r--src/main/java/eu/siacs/conversations/generator/IqGenerator.java5
-rw-r--r--src/main/java/eu/siacs/conversations/generator/MessageGenerator.java2
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java16
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java45
-rw-r--r--src/main/java/eu/siacs/conversations/parser/MessageParser.java20
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java269
-rw-r--r--src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java97
-rw-r--r--src/main/java/eu/siacs/conversations/services/NotificationService.java5
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java27
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java5
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationActivity.java30
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationFragment.java37
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java5
-rw-r--r--src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java22
-rw-r--r--src/main/java/eu/siacs/conversations/ui/XmppActivity.java65
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java72
-rw-r--r--src/main/java/eu/siacs/conversations/utils/DNSHelper.java16
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java3
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java97
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java10
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java6
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java26
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java5
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java3
32 files changed, 1635 insertions, 1279 deletions
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
index c6f74538a..255939a4f 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
@@ -6,28 +6,15 @@ import android.util.Log;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.whispersystems.libaxolotl.AxolotlAddress;
-import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair;
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.SessionBuilder;
-import org.whispersystems.libaxolotl.SessionCipher;
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.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.PreKeyRecord;
-import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import org.whispersystems.libaxolotl.util.KeyHelper;
@@ -73,547 +60,6 @@ public class AxolotlService {
private final FetchStatusMap fetchStatusMap;
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> {
protected Map<String, Map<Integer, T>> map;
protected final Object MAP_LOCK = new Object();
@@ -680,7 +126,7 @@ public class AxolotlService {
private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
for (Integer deviceId : deviceIds) {
AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building session for remote address: "+axolotlAddress.toString());
+ 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));
}
@@ -704,8 +150,13 @@ public class AxolotlService {
@Override
public void put(AxolotlAddress address, XmppAxolotlSession value) {
super.put(address, value);
+ value.setNotFresh();
xmppConnectionService.syncRosterToDisk(account);
}
+
+ public void put(XmppAxolotlSession session) {
+ this.put(session.getRemoteAddress(), session);
+ }
}
private static enum FetchStatus {
@@ -717,9 +168,9 @@ public class AxolotlService {
private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
}
-
+
public static String getLogprefix(Account account) {
- return LOGPREFIX+" ("+account.getJid().toBareJid().toString()+"): ";
+ return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): ";
}
public AxolotlService(Account account, XmppConnectionService connectionService) {
@@ -740,12 +191,12 @@ public class AxolotlService {
return axolotlStore.getIdentityKeyPair().getPublicKey();
}
- public Set<IdentityKey> getKeysWithTrust(SQLiteAxolotlStore.Trust trust) {
- return axolotlStore.getContactUndecidedKeys(account.getJid().toBareJid().toString(), trust);
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) {
+ return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust);
}
- public Set<IdentityKey> getKeysWithTrust(SQLiteAxolotlStore.Trust trust, Contact contact) {
- return axolotlStore.getContactUndecidedKeys(contact.getJid().toBareJid().toString(), trust);
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact) {
+ return axolotlStore.getContactKeysWithTrust(contact.getJid().toBareJid().toString(), trust);
}
public long getNumTrustedKeys(Contact contact) {
@@ -782,7 +233,7 @@ public class AxolotlService {
}
public int getOwnDeviceId() {
- return axolotlStore.loadRegistrationId();
+ return axolotlStore.getLocalRegistrationId();
}
public Set<Integer> getOwnDeviceIds() {
@@ -790,9 +241,9 @@ public class AxolotlService {
}
private void setTrustOnSessions(final Jid jid, @NonNull final Set<Integer> deviceIds,
- final SQLiteAxolotlStore.Trust from,
- final SQLiteAxolotlStore.Trust to) {
- for(Integer deviceId:deviceIds) {
+ final XmppAxolotlSession.Trust from,
+ final XmppAxolotlSession.Trust to) {
+ for (Integer deviceId : deviceIds) {
AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
XmppAxolotlSession session = sessions.get(address);
if (session != null && session.getFingerprint() != null
@@ -803,24 +254,32 @@ public class AxolotlService {
}
public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
- if(jid.toBareJid().equals(account.getJid().toBareJid())) {
+ if (jid.toBareJid().equals(account.getJid().toBareJid())) {
if (deviceIds.contains(getOwnDeviceId())) {
deviceIds.remove(getOwnDeviceId());
}
- for(Integer deviceId : deviceIds) {
- AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(),deviceId);
- if(sessions.get(ownDeviceAddress) == null) {
- buildSessionFromPEP(null, ownDeviceAddress, false);
+ for (Integer deviceId : deviceIds) {
+ AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
+ if (sessions.get(ownDeviceAddress) == null) {
+ buildSessionFromPEP(ownDeviceAddress);
}
}
}
Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString()));
expiredDevices.removeAll(deviceIds);
- setTrustOnSessions(jid, expiredDevices, SQLiteAxolotlStore.Trust.TRUSTED,
- SQLiteAxolotlStore.Trust.INACTIVE);
+ setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED,
+ XmppAxolotlSession.Trust.INACTIVE_TRUSTED);
+ setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNDECIDED,
+ XmppAxolotlSession.Trust.INACTIVE_UNDECIDED);
+ setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNTRUSTED,
+ XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED);
Set<Integer> newDevices = new HashSet<>(deviceIds);
- setTrustOnSessions(jid, newDevices, SQLiteAxolotlStore.Trust.INACTIVE,
- SQLiteAxolotlStore.Trust.TRUSTED);
+ setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED,
+ XmppAxolotlSession.Trust.TRUSTED);
+ setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNDECIDED,
+ XmppAxolotlSession.Trust.UNDECIDED);
+ setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED,
+ XmppAxolotlSession.Trust.UNTRUSTED);
this.deviceIds.put(jid, deviceIds);
mXmppConnectionService.keyStatusUpdated();
publishOwnDeviceIdIfNeeded();
@@ -830,7 +289,7 @@ public class AxolotlService {
Set<Integer> deviceIds = new HashSet<>();
deviceIds.add(getOwnDeviceId());
IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Wiping all other devices from Pep:" + publish);
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish);
mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
@@ -840,7 +299,7 @@ public class AxolotlService {
}
public void purgeKey(IdentityKey identityKey) {
- axolotlStore.setFingerprintTrust(identityKey.getFingerprint().replaceAll("\\s",""), SQLiteAxolotlStore.Trust.COMPROMISED);
+ axolotlStore.setFingerprintTrust(identityKey.getFingerprint().replaceAll("\\s", ""), XmppAxolotlSession.Trust.COMPROMISED);
}
public void publishOwnDeviceIdIfNeeded() {
@@ -856,7 +315,7 @@ public class AxolotlService {
if (!deviceIds.contains(getOwnDeviceId())) {
deviceIds.add(getOwnDeviceId());
IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Own device " + getOwnDeviceId() + " not in PEP devicelist. Publishing: " + publish);
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist. Publishing: " + publish);
mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
@@ -877,19 +336,19 @@ public class AxolotlService {
Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
boolean flush = false;
if (bundle == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received invalid bundle:" + packet);
- bundle = new PreKeyBundle(-1, -1, -1 , null, -1, null, null, null);
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet);
+ bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
flush = true;
}
if (keys == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received invalid prekeys:" + packet);
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet);
}
try {
boolean changed = false;
// Validate IdentityKey
IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
changed = true;
}
@@ -898,16 +357,16 @@ public class AxolotlService {
int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size();
try {
signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
- if ( flush
- ||!bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
+ if (flush
+ || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
|| !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
changed = true;
}
} catch (InvalidKeyIdException e) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
changed = true;
@@ -929,32 +388,32 @@ public class AxolotlService {
int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
if (newKeys > 0) {
List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
- axolotlStore.getCurrentPreKeyId()+1, newKeys);
+ axolotlStore.getCurrentPreKeyId() + 1, newKeys);
preKeyRecords.addAll(newRecords);
for (PreKeyRecord record : newRecords) {
axolotlStore.storePreKey(record.getId(), record);
}
changed = true;
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding " + newKeys + " new preKeys to PEP.");
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
}
- if(changed) {
+ if (changed) {
IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
preKeyRecords, getOwnDeviceId());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+ ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
// TODO: implement this!
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Published bundle, got: " + packet);
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Published bundle, got: " + packet);
}
});
}
} catch (InvalidKeyException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
- return;
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
+ return;
}
}
});
@@ -965,49 +424,41 @@ public class AxolotlService {
Jid jid = contact.getJid().toBareJid();
AxolotlAddress address = new AxolotlAddress(jid.toString(), 0);
return sessions.hasAny(address) ||
- ( deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty());
+ (deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty());
}
- public SQLiteAxolotlStore.Trust getFingerprintTrust(String fingerprint) {
+
+ public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
return axolotlStore.getFingerprintTrust(fingerprint);
}
- public void setFingerprintTrust(String fingerprint, SQLiteAxolotlStore.Trust trust) {
+ public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
axolotlStore.setFingerprintTrust(fingerprint, trust);
}
- private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address, final boolean flushWaitingQueueAfterFetch) {
+ private void buildSessionFromPEP(final AxolotlAddress address) {
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.getDeviceId());
try {
IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
Jid.fromString(address.getName()), address.getDeviceId());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Retrieving bundle: " + bundlesPacket);
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
private void finish() {
- AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(),0);
+ AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
&& !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) {
- if (flushWaitingQueueAfterFetch && conversation != null) {
- conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_AXOLOTL,
- new Conversation.OnMessageFound() {
- @Override
- public void onMessageFound(Message message) {
- processSending(message,false);
- }
- });
- }
mXmppConnectionService.keyStatusUpdated();
}
}
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received preKey IQ packet, processing...");
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
final IqParser parser = mXmppConnectionService.getIqParser();
final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
final PreKeyBundle bundle = parser.bundle(packet);
if (preKeyBundleList.isEmpty() || bundle == null) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"preKey IQ packet invalid: " + packet);
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
fetchStatusMap.put(address, FetchStatus.ERROR);
finish();
return;
@@ -1034,8 +485,8 @@ public class AxolotlService {
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) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error building session for " + address + ": "
+ } catch (UntrustedIdentityException | InvalidKeyException e) {
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
+ e.getClass().getName() + ", " + e.getMessage());
fetchStatusMap.put(address, FetchStatus.ERROR);
}
@@ -1044,7 +495,7 @@ public class AxolotlService {
}
});
} catch (InvalidJidException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got address with invalid jid: " + address.getName());
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName());
}
}
@@ -1052,13 +503,13 @@ public class AxolotlService {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + conversation.getContact().getJid().toBareJid());
Jid contactJid = conversation.getContact().getJid().toBareJid();
Set<AxolotlAddress> addresses = new HashSet<>();
- if(deviceIds.get(contactJid) != null) {
- for(Integer foreignId:this.deviceIds.get(contactJid)) {
+ if (deviceIds.get(contactJid) != null) {
+ for (Integer foreignId : this.deviceIds.get(contactJid)) {
AxolotlAddress address = new AxolotlAddress(contactJid.toString(), foreignId);
- if(sessions.get(address) == null) {
+ if (sessions.get(address) == null) {
IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- if ( identityKey != null ) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache...");
+ if (identityKey != null) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", ""));
sessions.put(address, session);
} else {
@@ -1070,13 +521,13 @@ public class AxolotlService {
} else {
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
}
- if(deviceIds.get(account.getJid().toBareJid()) != null) {
- for(Integer ownId:this.deviceIds.get(account.getJid().toBareJid())) {
+ if (deviceIds.get(account.getJid().toBareJid()) != null) {
+ for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId);
- if(sessions.get(address) == null) {
+ if (sessions.get(address) == null) {
IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- if ( identityKey != null ) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache...");
+ if (identityKey != null) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", ""));
sessions.put(address, session);
} else {
@@ -1090,19 +541,21 @@ public class AxolotlService {
return addresses;
}
- public boolean createSessionsIfNeeded(final Conversation conversation, final boolean flushWaitingQueueAfterFetch) {
+ public boolean createSessionsIfNeeded(final Conversation conversation) {
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
boolean newSessions = false;
Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
for (AxolotlAddress address : addresses) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
FetchStatus status = fetchStatusMap.get(address);
- if ( status == null || status == FetchStatus.ERROR ) {
- fetchStatusMap.put(address, FetchStatus.PENDING);
- this.buildSessionFromPEP(conversation, address, flushWaitingQueueAfterFetch);
- newSessions = true;
+ if (status == null || status == FetchStatus.ERROR) {
+ fetchStatusMap.put(address, FetchStatus.PENDING);
+ this.buildSessionFromPEP(address);
+ newSessions = true;
+ } else if (status == FetchStatus.PENDING) {
+ newSessions = true;
} else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already fetching bundle for " + address.toString());
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
}
}
@@ -1110,56 +563,60 @@ public class AxolotlService {
}
public boolean hasPendingKeyFetches(Conversation conversation) {
- AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(),0);
- AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(),0);
+ AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
+ AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(), 0);
return fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
- ||fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING);
+ || fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING);
}
@Nullable
- public XmppAxolotlMessage encrypt(Message message ){
- final String content;
- if (message.hasFileOnRemoteHost()) {
- content = message.getFileParams().url.toString();
- } else {
- content = message.getBody();
- }
- final XmppAxolotlMessage axolotlMessage;
- try {
- axolotlMessage = new XmppAxolotlMessage(message.getContact().getJid().toBareJid(),
- getOwnDeviceId(), content);
- } catch (CryptoFailedException e) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
- return null;
- }
+ private XmppAxolotlMessage buildHeader(Contact contact) {
+ final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
+ contact.getJid().toBareJid(), getOwnDeviceId());
- if(findSessionsforContact(message.getContact()).isEmpty()) {
+ Set<XmppAxolotlSession> contactSessions = findSessionsforContact(contact);
+ Set<XmppAxolotlSession> ownSessions = findOwnSessions();
+ if (contactSessions.isEmpty()) {
return null;
}
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign headers...");
- for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) {
- Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.remoteAddress.toString());
- //if(!session.isTrusted()) {
- // TODO: handle this properly
- // continue;
- // }
- axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey()));
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements...");
+ for (XmppAxolotlSession session : contactSessions) {
+ Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
+ axolotlMessage.addDevice(session);
}
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own headers...");
- for (XmppAxolotlSession session : findOwnSessions()) {
- Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.remoteAddress.toString());
- // if(!session.isTrusted()) {
- // TODO: handle this properly
- // continue;
- // }
- axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey()));
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements...");
+ for (XmppAxolotlSession session : ownSessions) {
+ Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
+ axolotlMessage.addDevice(session);
+ }
+
+ return axolotlMessage;
+ }
+
+ @Nullable
+ public XmppAxolotlMessage encrypt(Message message) {
+ XmppAxolotlMessage axolotlMessage = buildHeader(message.getContact());
+
+ if (axolotlMessage != null) {
+ final String content;
+ if (message.hasFileOnRemoteHost()) {
+ content = message.getFileParams().url.toString();
+ } else {
+ content = message.getBody();
+ }
+ try {
+ axolotlMessage.encrypt(content);
+ } catch (CryptoFailedException e) {
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
+ return null;
+ }
}
return axolotlMessage;
}
- private void processSending(final Message message, final boolean delay) {
+ public void preparePayloadMessage(final Message message, final boolean delay) {
executor.execute(new Runnable() {
@Override
public void run() {
@@ -1168,79 +625,89 @@ public class AxolotlService {
mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
//mXmppConnectionService.updateConversationUi();
} else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Generated message, caching: " + message.getUuid());
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
messageCache.put(message.getUuid(), axolotlMessage);
- mXmppConnectionService.resendMessage(message,delay);
+ mXmppConnectionService.resendMessage(message, delay);
}
}
});
}
- public void prepareMessage(final Message message,final boolean delay) {
- if (!messageCache.containsKey(message.getUuid())) {
- boolean newSessions = createSessionsIfNeeded(message.getConversation(), true);
- if (!newSessions) {
- this.processSending(message,delay);
+ public void prepareKeyTransportMessage(final Contact contact, final OnMessageCreatedCallback onMessageCreatedCallback) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ XmppAxolotlMessage axolotlMessage = buildHeader(contact);
+ onMessageCreatedCallback.run(axolotlMessage);
}
- }
+ });
}
public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
if (axolotlMessage != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Cache hit: " + message.getUuid());
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
messageCache.remove(message.getUuid());
} else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Cache miss: " + message.getUuid());
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
}
return axolotlMessage;
}
- public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) {
- XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
+ private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) {
+ IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
+ return (identityKey != null)
+ ? new XmppAxolotlSession(account, axolotlStore, address,
+ identityKey.getFingerprint().replaceAll("\\s", ""))
+ : null;
+ }
+
+ private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(),
message.getSenderDeviceId());
-
- boolean newSession = false;
XmppAxolotlSession session = sessions.get(senderAddress);
if (session == null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Account: "+account.getJid()+" No axolotl session found while parsing received message " + message);
- // TODO: handle this properly
- IdentityKey identityKey = axolotlStore.loadSession(senderAddress).getSessionState().getRemoteIdentityKey();
- if ( identityKey != null ) {
- session = new XmppAxolotlSession(account, axolotlStore, senderAddress, identityKey.getFingerprint().replaceAll("\\s", ""));
- } else {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
+ session = recreateUncachedSession(senderAddress);
+ if (session == null) {
session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
}
- newSession = true;
}
+ return session;
+ }
- for (XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) {
- if (header.getRecipientDeviceId() == getOwnDeviceId()) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl header matching own device ID, processing...");
- byte[] payloadKey = session.processReceiving(header);
- if (payloadKey != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl header. Decrypting message...");
- try{
- plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint());
- } catch (CryptoFailedException e) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
- break;
- }
- }
- Integer preKeyId = session.getPreKeyId();
- if (preKeyId != null) {
- publishBundlesIfNeeded();
- session.resetPreKeyId();
- }
- break;
+ public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
+ XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
+
+ XmppAxolotlSession session = getReceivingSession(message);
+ try {
+ plaintextMessage = message.decrypt(session, getOwnDeviceId());
+ Integer preKeyId = session.getPreKeyId();
+ if (preKeyId != null) {
+ publishBundlesIfNeeded();
+ session.resetPreKeyId();
}
+ } catch (CryptoFailedException e) {
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
}
- if (newSession && plaintextMessage != null) {
- sessions.put(senderAddress, session);
+ if (session.isFresh() && plaintextMessage != null) {
+ sessions.put(session);
}
return plaintextMessage;
}
+
+ public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
+ XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage = null;
+
+ XmppAxolotlSession session = getReceivingSession(message);
+ keyTransportMessage = message.getParameters(session, getOwnDeviceId());
+
+ if (session.isFresh() && keyTransportMessage != null) {
+ sessions.put(session);
+ }
+
+ return keyTransportMessage;
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java
new file mode 100644
index 000000000..3d40a4089
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java
@@ -0,0 +1,5 @@
+package eu.siacs.conversations.crypto.axolotl;
+
+public interface OnMessageCreatedCallback {
+ void run(XmppAxolotlMessage message);
+}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
new file mode 100644
index 000000000..190eb88a8
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
@@ -0,0 +1,421 @@
+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.List;
+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, XmppAxolotlSession.Trust> trustCache =
+ new LruCache<String, XmppAxolotlSession.Trust>(NUM_TRUSTS_TO_CACHE) {
+ @Override
+ protected XmppAxolotlSession.Trust create(String fingerprint) {
+ return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint);
+ }
+ };
+
+ 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 XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
+ return (fingerprint == null)? null : trustCache.get(fingerprint);
+ }
+
+ public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
+ mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust);
+ trustCache.remove(fingerprint);
+ }
+
+ public Set<IdentityKey> getContactKeysWithTrust(String bareJid, XmppAxolotlSession.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);
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
index 24afeaeae..cf950d6da 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
@@ -1,15 +1,16 @@
package eu.siacs.conversations.crypto.axolotl;
-import android.support.annotation.Nullable;
import android.util.Base64;
+import android.util.Log;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@@ -20,112 +21,142 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
+import eu.siacs.conversations.Config;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.jid.Jid;
public class XmppAxolotlMessage {
+ public static final String CONTAINERTAG = "encrypted";
+ public static final String HEADER = "header";
+ public static final String SOURCEID = "sid";
+ public static final String KEYTAG = "key";
+ public static final String REMOTEID = "rid";
+ public static final String IVTAG = "iv";
+ public static final String PAYLOAD = "payload";
+
+ private static final String KEYTYPE = "AES";
+ private static final String CIPHERMODE = "AES/GCM/NoPadding";
+ private static final String PROVIDER = "BC";
+
private byte[] innerKey;
- private byte[] ciphertext;
- private byte[] iv;
- private final Set<XmppAxolotlMessageHeader> headers;
+ private byte[] ciphertext = null;
+ private byte[] iv = null;
+ private final Map<Integer, byte[]> keys;
private final Jid from;
private final int sourceDeviceId;
- public static class XmppAxolotlMessageHeader {
- private final int recipientDeviceId;
- private final byte[] content;
-
- public XmppAxolotlMessageHeader(int deviceId, byte[] content) {
- this.recipientDeviceId = deviceId;
- this.content = content;
- }
+ public static class XmppAxolotlPlaintextMessage {
+ private final String plaintext;
+ private final String fingerprint;
- public XmppAxolotlMessageHeader(Element header) {
- if("header".equals(header.getName())) {
- this.recipientDeviceId = Integer.parseInt(header.getAttribute("rid"));
- this.content = Base64.decode(header.getContent(),Base64.DEFAULT);
- } else {
- throw new IllegalArgumentException("Argument not a <header> Element!");
- }
+ public XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) {
+ this.plaintext = plaintext;
+ this.fingerprint = fingerprint;
}
- public int getRecipientDeviceId() {
- return recipientDeviceId;
+ public String getPlaintext() {
+ return plaintext;
}
- public byte[] getContents() {
- return content;
- }
- public Element toXml() {
- Element headerElement = new Element("header");
- // TODO: generate XML
- headerElement.setAttribute("rid", getRecipientDeviceId());
- headerElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT));
- return headerElement;
+ public String getFingerprint() {
+ return fingerprint;
}
}
- public static class XmppAxolotlPlaintextMessage {
- private final AxolotlService.XmppAxolotlSession session;
- private final String plaintext;
+ public static class XmppAxolotlKeyTransportMessage {
private final String fingerprint;
+ private final byte[] key;
+ private final byte[] iv;
- public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext, String fingerprint) {
- this.session = session;
- this.plaintext = plaintext;
+ public XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) {
this.fingerprint = fingerprint;
+ this.key = key;
+ this.iv = iv;
}
- public String getPlaintext() {
- return plaintext;
+ public String getFingerprint() {
+ return fingerprint;
}
- public AxolotlService.XmppAxolotlSession getSession() {
- return session;
+ public byte[] getKey() {
+ return key;
}
- public String getFingerprint() {
- return fingerprint;
+ public byte[] getIv() {
+ return iv;
}
}
- public XmppAxolotlMessage(Jid from, Element axolotlMessage) {
+ private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException {
this.from = from;
- this.sourceDeviceId = Integer.parseInt(axolotlMessage.getAttribute("id"));
- this.headers = new HashSet<>();
- for(Element child:axolotlMessage.getChildren()) {
- switch(child.getName()) {
- case "header":
- headers.add(new XmppAxolotlMessageHeader(child));
+ Element header = axolotlMessage.findChild(HEADER);
+ this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID));
+ List<Element> keyElements = header.getChildren();
+ this.keys = new HashMap<>(keyElements.size());
+ for (Element keyElement : keyElements) {
+ switch (keyElement.getName()) {
+ case KEYTAG:
+ try {
+ Integer recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID));
+ byte[] key = Base64.decode(keyElement.getContent(), Base64.DEFAULT);
+ this.keys.put(recipientId, key);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(e);
+ }
break;
- case "message":
- iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT);
- ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT);
+ case IVTAG:
+ if (this.iv != null) {
+ throw new IllegalArgumentException("Duplicate iv entry");
+ }
+ iv = Base64.decode(keyElement.getContent(), Base64.DEFAULT);
break;
default:
+ Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement.toString());
break;
}
}
+ Element payloadElement = axolotlMessage.findChild(PAYLOAD);
+ if (payloadElement != null) {
+ ciphertext = Base64.decode(payloadElement.getContent(), Base64.DEFAULT);
+ }
}
- public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException{
+ public XmppAxolotlMessage(Jid from, int sourceDeviceId) {
this.from = from;
this.sourceDeviceId = sourceDeviceId;
- this.headers = new HashSet<>();
- this.encrypt(plaintext);
+ this.keys = new HashMap<>();
+ this.iv = generateIv();
+ this.innerKey = generateKey();
}
- private void encrypt(String plaintext) throws CryptoFailedException {
+ public static XmppAxolotlMessage fromElement(Element element, Jid from) {
+ return new XmppAxolotlMessage(element, from);
+ }
+
+ private static byte[] generateKey() {
try {
- KeyGenerator generator = KeyGenerator.getInstance("AES");
+ KeyGenerator generator = KeyGenerator.getInstance(KEYTYPE);
generator.init(128);
- SecretKey secretKey = generator.generateKey();
- SecureRandom random = new SecureRandom();
- this.iv = new byte[16];
- random.nextBytes(iv);
+ return generator.generateKey().getEncoded();
+ } catch (NoSuchAlgorithmException e) {
+ Log.e(Config.LOGTAG, e.getMessage());
+ return null;
+ }
+ }
+
+ private static byte[] generateIv() {
+ SecureRandom random = new SecureRandom();
+ byte[] iv = new byte[16];
+ random.nextBytes(iv);
+ return iv;
+ }
+
+ public void encrypt(String plaintext) throws CryptoFailedException {
+ try {
+ SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
- Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
+ Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
this.innerKey = secretKey.getEncoded();
this.ciphertext = cipher.doFinal(plaintext.getBytes());
@@ -148,17 +179,14 @@ public class XmppAxolotlMessage {
return ciphertext;
}
- public Set<XmppAxolotlMessageHeader> getHeaders() {
- return headers;
- }
-
- public void addHeader(@Nullable XmppAxolotlMessageHeader header) {
- if (header != null) {
- headers.add(header);
+ public void addDevice(XmppAxolotlSession session) {
+ byte[] key = session.processSending(innerKey);
+ if (key != null) {
+ keys.put(session.getRemoteAddress().getDeviceId(), key);
}
}
- public byte[] getInnerKey(){
+ public byte[] getInnerKey() {
return innerKey;
}
@@ -166,37 +194,55 @@ public class XmppAxolotlMessage {
return this.iv;
}
- public Element toXml() {
- // TODO: generate outer XML, add in header XML
- Element message= new Element("axolotl_message", AxolotlService.PEP_PREFIX);
- message.setAttribute("id", sourceDeviceId);
- for(XmppAxolotlMessageHeader header: headers) {
- message.addChild(header.toXml());
+ public Element toElement() {
+ Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX);
+ Element headerElement = encryptionElement.addChild(HEADER);
+ headerElement.setAttribute(SOURCEID, sourceDeviceId);
+ for (Map.Entry<Integer, byte[]> keyEntry : keys.entrySet()) {
+ Element keyElement = new Element(KEYTAG);
+ keyElement.setAttribute(REMOTEID, keyEntry.getKey());
+ keyElement.setContent(Base64.encodeToString(keyEntry.getValue(), Base64.DEFAULT));
+ headerElement.addChild(keyElement);
+ }
+ headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT));
+ if (ciphertext != null) {
+ Element payload = encryptionElement.addChild(PAYLOAD);
+ payload.setContent(Base64.encodeToString(ciphertext, Base64.DEFAULT));
}
- Element payload = message.addChild("message");
- payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT));
- payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT));
- return message;
+ return encryptionElement;
}
+ private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) {
+ byte[] encryptedKey = keys.get(sourceDeviceId);
+ return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null;
+ }
- public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) throws CryptoFailedException {
- XmppAxolotlPlaintextMessage plaintextMessage = null;
- try {
-
- Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
- SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
- IvParameterSpec ivSpec = new IvParameterSpec(iv);
-
- cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
-
- String plaintext = new String(cipher.doFinal(ciphertext));
- plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext, fingerprint);
+ public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) {
+ byte[] key = unpackKey(session, sourceDeviceId);
+ return (key != null)
+ ? new XmppAxolotlKeyTransportMessage(session.getFingerprint(), key, getIV())
+ : null;
+ }
- } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
- | InvalidAlgorithmParameterException | IllegalBlockSizeException
- | BadPaddingException | NoSuchProviderException e) {
- throw new CryptoFailedException(e);
+ public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException {
+ XmppAxolotlPlaintextMessage plaintextMessage = null;
+ byte[] key = unpackKey(session, sourceDeviceId);
+ if (key != null) {
+ try {
+ Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
+ SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
+ IvParameterSpec ivSpec = new IvParameterSpec(iv);
+
+ cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
+
+ String plaintext = new String(cipher.doFinal(ciphertext));
+ plaintextMessage = new XmppAxolotlPlaintextMessage(plaintext, session.getFingerprint());
+
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | NoSuchProviderException e) {
+ throw new CryptoFailedException(e);
+ }
}
return plaintextMessage;
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
new file mode 100644
index 000000000..c4053854d
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
@@ -0,0 +1,196 @@
+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 java.util.HashMap;
+import java.util.Map;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Account;
+
+public class XmppAxolotlSession {
+ private final SessionCipher cipher;
+ private final SQLiteAxolotlStore sqLiteAxolotlStore;
+ private final AxolotlAddress remoteAddress;
+ private final Account account;
+ private String fingerprint = null;
+ private Integer preKeyId = null;
+ private boolean fresh = true;
+
+ public enum Trust {
+ UNDECIDED(0),
+ TRUSTED(1),
+ UNTRUSTED(2),
+ COMPROMISED(3),
+ INACTIVE_TRUSTED(4),
+ INACTIVE_UNDECIDED(5),
+ INACTIVE_UNTRUSTED(6);
+
+ 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_TRUSTED:
+ return "Inactive (Trusted)" + getCode();
+ case INACTIVE_UNDECIDED:
+ return "Inactive (Undecided)" + getCode();
+ case INACTIVE_UNTRUSTED:
+ return "Inactive (Untrusted)" + 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);
+ }
+ }
+
+ 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;
+ }
+
+ public AxolotlAddress getRemoteAddress() {
+ return remoteAddress;
+ }
+
+ public boolean isFresh() {
+ return fresh;
+ }
+
+ public void setNotFresh() {
+ this.fresh = false;
+ }
+
+ protected void setTrust(Trust trust) {
+ sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust);
+ }
+
+ protected Trust getTrust() {
+ Trust trust = sqLiteAxolotlStore.getFingerprintTrust(fingerprint);
+ return (trust == null) ? Trust.UNDECIDED : trust;
+ }
+
+ @Nullable
+ public byte[] processReceiving(byte[] encryptedKey) {
+ byte[] plaintext = null;
+ Trust trust = getTrust();
+ switch (trust) {
+ case INACTIVE_TRUSTED:
+ case UNDECIDED:
+ case UNTRUSTED:
+ case TRUSTED:
+ try {
+ try {
+ PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey);
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
+ String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", "");
+ if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) {
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.fingerprint + ", received message with fingerprint " + fingerprint);
+ } 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(encryptedKey);
+ 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 == Trust.INACTIVE_TRUSTED) {
+ setTrust(Trust.TRUSTED);
+ }
+
+ break;
+
+ case COMPROMISED:
+ default:
+ // ignore
+ break;
+ }
+ return plaintext;
+ }
+
+ @Nullable
+ public byte[] processSending(@NonNull byte[] outgoingMessage) {
+ Trust trust = getTrust();
+ if (trust == Trust.TRUSTED) {
+ CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
+ return ciphertextMessage.serialize();
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java
index 2e858eb99..3a73f78bf 100644
--- a/src/main/java/eu/siacs/conversations/entities/Conversation.java
+++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java
@@ -563,42 +563,51 @@ public class Conversation extends AbstractEntity implements Blockable {
return this.nextCounterpart;
}
- public int getLatestEncryption() {
- int latestEncryption = this.getLatestMessage().getEncryption();
- if ((latestEncryption == Message.ENCRYPTION_DECRYPTED)
- || (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) {
- return Message.ENCRYPTION_PGP;
- } else {
- return latestEncryption;
+ private int getMostRecentlyUsedOutgoingEncryption() {
+ synchronized (this.messages) {
+ for(int i = this.messages.size() -1; i >= 0; --i) {
+ final Message m = this.messages.get(0);
+ if (!m.isCarbon() && m.getStatus() != Message.STATUS_RECEIVED) {
+ final int e = m.getEncryption();
+ if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) {
+ return Message.ENCRYPTION_PGP;
+ } else {
+ return e;
+ }
+ }
+ }
}
+ return Message.ENCRYPTION_NONE;
}
- public int getNextEncryption(boolean force) {
- int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
- if (next == -1) {
- int latest = this.getLatestEncryption();
- if (latest == Message.ENCRYPTION_NONE) {
- if (force && getMode() == MODE_SINGLE) {
- return Message.ENCRYPTION_OTR;
- } else if (getContact().getPresences().size() == 1) {
- if (getContact().getOtrFingerprints().size() >= 1) {
- return Message.ENCRYPTION_OTR;
+ private int getMostRecentlyUsedIncomingEncryption() {
+ synchronized (this.messages) {
+ for(int i = this.messages.size() -1; i >= 0; --i) {
+ final Message m = this.messages.get(0);
+ if (m.getStatus() == Message.STATUS_RECEIVED) {
+ final int e = m.getEncryption();
+ if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) {
+ return Message.ENCRYPTION_PGP;
} else {
- return latest;
+ return e;
}
- } else {
- return latest;
}
- } else {
- return latest;
}
}
- if (next == Message.ENCRYPTION_NONE && force
- && getMode() == MODE_SINGLE) {
- return Message.ENCRYPTION_OTR;
- } else {
- return next;
+ return Message.ENCRYPTION_NONE;
+ }
+
+ public int getNextEncryption() {
+ int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
+ if (next == -1) {
+ int outgoing = this.getMostRecentlyUsedOutgoingEncryption();
+ if (outgoing == Message.ENCRYPTION_NONE) {
+ return this.getMostRecentlyUsedIncomingEncryption();
+ } else {
+ return outgoing;
+ }
}
+ return next;
}
public void setNextEncryption(int encryption) {
diff --git a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java
index ca3254484..d35a4b017 100644
--- a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java
+++ b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java
@@ -1,26 +1,7 @@
package eu.siacs.conversations.entities;
-import android.util.Log;
-
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import eu.siacs.conversations.Config;
+
import eu.siacs.conversations.utils.MimeUtils;
public class DownloadableFile extends File {
@@ -29,8 +10,7 @@ public class DownloadableFile extends File {
private long expectedSize = 0;
private String sha1sum;
- private Key aeskey;
- private String mime;
+ private byte[] aeskey;
private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
@@ -44,15 +24,7 @@ public class DownloadableFile extends File {
}
public long getExpectedSize() {
- if (this.aeskey != null) {
- if (this.expectedSize == 0) {
- return 0;
- } else {
- return (this.expectedSize / 16 + 1) * 16;
- }
- } else {
- return this.expectedSize;
- }
+ return this.expectedSize;
}
public String getMimeType() {
@@ -78,91 +50,38 @@ public class DownloadableFile extends File {
this.sha1sum = sum;
}
- public void setKey(byte[] key) {
- if (key.length == 48) {
+ public void setKeyAndIv(byte[] keyIvCombo) {
+ if (keyIvCombo.length == 48) {
byte[] secretKey = new byte[32];
byte[] iv = new byte[16];
- System.arraycopy(key, 0, iv, 0, 16);
- System.arraycopy(key, 16, secretKey, 0, 32);
- this.aeskey = new SecretKeySpec(secretKey, "AES");
+ System.arraycopy(keyIvCombo, 0, iv, 0, 16);
+ System.arraycopy(keyIvCombo, 16, secretKey, 0, 32);
+ this.aeskey = secretKey;
this.iv = iv;
- } else if (key.length >= 32) {
+ } else if (keyIvCombo.length >= 32) {
byte[] secretKey = new byte[32];
- System.arraycopy(key, 0, secretKey, 0, 32);
- this.aeskey = new SecretKeySpec(secretKey, "AES");
- } else if (key.length >= 16) {
+ System.arraycopy(keyIvCombo, 0, secretKey, 0, 32);
+ this.aeskey = secretKey;
+ } else if (keyIvCombo.length >= 16) {
byte[] secretKey = new byte[16];
- System.arraycopy(key, 0, secretKey, 0, 16);
- this.aeskey = new SecretKeySpec(secretKey, "AES");
+ System.arraycopy(keyIvCombo, 0, secretKey, 0, 16);
+ this.aeskey = secretKey;
}
}
- public Key getKey() {
- return this.aeskey;
+ public void setKey(byte[] key) {
+ this.aeskey = key;
}
- public InputStream createInputStream() {
- if (this.getKey() == null) {
- try {
- return new FileInputStream(this);
- } catch (FileNotFoundException e) {
- return null;
- }
- } else {
- try {
- IvParameterSpec ips = new IvParameterSpec(iv);
- Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
- cipher.init(Cipher.ENCRYPT_MODE, this.getKey(), ips);
- Log.d(Config.LOGTAG, "opening encrypted input stream");
- return new CipherInputStream(new FileInputStream(this), cipher);
- } catch (NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
- return null;
- } catch (NoSuchPaddingException e) {
- Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
- return null;
- } catch (InvalidKeyException e) {
- Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
- return null;
- } catch (InvalidAlgorithmParameterException e) {
- Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
- return null;
- } catch (FileNotFoundException e) {
- return null;
- }
- }
+ public void setIv(byte[] iv) {
+ this.iv = iv;
}
- public OutputStream createOutputStream() {
- if (this.getKey() == null) {
- try {
- return new FileOutputStream(this);
- } catch (FileNotFoundException e) {
- return null;
- }
- } else {
- try {
- IvParameterSpec ips = new IvParameterSpec(this.iv);
- Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
- cipher.init(Cipher.DECRYPT_MODE, this.getKey(), ips);
- Log.d(Config.LOGTAG, "opening encrypted output stream");
- return new CipherOutputStream(new FileOutputStream(this),
- cipher);
- } catch (NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
- return null;
- } catch (NoSuchPaddingException e) {
- Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
- return null;
- } catch (InvalidKeyException e) {
- Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
- return null;
- } catch (InvalidAlgorithmParameterException e) {
- Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
- return null;
- } catch (FileNotFoundException e) {
- return null;
- }
- }
+ public byte[] getKey() {
+ return this.aeskey;
+ }
+
+ public byte[] getIv() {
+ return this.iv;
}
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java
index 698775c8a..0eff99cf7 100644
--- a/src/main/java/eu/siacs/conversations/entities/Message.java
+++ b/src/main/java/eu/siacs/conversations/entities/Message.java
@@ -8,7 +8,7 @@ import java.net.URL;
import java.util.Arrays;
import eu.siacs.conversations.Config;
-import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.MimeUtils;
import eu.siacs.conversations.utils.UIHelper;
@@ -51,6 +51,7 @@ public class Message extends AbstractEntity {
public static final String ENCRYPTION = "encryption";
public static final String STATUS = "status";
public static final String TYPE = "type";
+ public static final String CARBON = "carbon";
public static final String REMOTE_MSG_ID = "remoteMsgId";
public static final String SERVER_MSG_ID = "serverMsgId";
public static final String RELATIVE_FILE_PATH = "relativeFilePath";
@@ -68,6 +69,7 @@ public class Message extends AbstractEntity {
protected int encryption;
protected int status;
protected int type;
+ protected boolean carbon = false;
protected String relativeFilePath;
protected boolean read = true;
protected String remoteMsgId = null;
@@ -85,8 +87,11 @@ public class Message extends AbstractEntity {
public Message(Conversation conversation, String body, int encryption) {
this(conversation, body, encryption, STATUS_UNSEND);
}
-
public Message(Conversation conversation, String body, int encryption, int status) {
+ this(conversation, body, encryption, status, false);
+ }
+
+ public Message(Conversation conversation, String body, int encryption, int status, boolean carbon) {
this(java.util.UUID.randomUUID().toString(),
conversation.getUuid(),
conversation.getJid() == null ? null : conversation.getJid().toBareJid(),
@@ -96,6 +101,7 @@ public class Message extends AbstractEntity {
encryption,
status,
TYPE_TEXT,
+ false,
null,
null,
null,
@@ -105,8 +111,9 @@ 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 fingerprint) {
+ final int encryption, final int status, final int type, final boolean carbon,
+ final String remoteMsgId, final String relativeFilePath,
+ final String serverMsgId, final String fingerprint) {
this.uuid = uuid;
this.conversationUuid = conversationUUid;
this.counterpart = counterpart;
@@ -116,6 +123,7 @@ public class Message extends AbstractEntity {
this.encryption = encryption;
this.status = status;
this.type = type;
+ this.carbon = carbon;
this.remoteMsgId = remoteMsgId;
this.relativeFilePath = relativeFilePath;
this.serverMsgId = serverMsgId;
@@ -154,6 +162,7 @@ public class Message extends AbstractEntity {
cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
cursor.getInt(cursor.getColumnIndex(STATUS)),
cursor.getInt(cursor.getColumnIndex(TYPE)),
+ cursor.getInt(cursor.getColumnIndex(CARBON))>0,
cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)),
cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)),
cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)),
@@ -188,6 +197,7 @@ public class Message extends AbstractEntity {
values.put(ENCRYPTION, encryption);
values.put(STATUS, status);
values.put(TYPE, type);
+ values.put(CARBON, carbon ? 1 : 0);
values.put(REMOTE_MSG_ID, remoteMsgId);
values.put(RELATIVE_FILE_PATH, relativeFilePath);
values.put(SERVER_MSG_ID, serverMsgId);
@@ -312,6 +322,14 @@ public class Message extends AbstractEntity {
this.type = type;
}
+ public boolean isCarbon() {
+ return carbon;
+ }
+
+ public void setCarbon(boolean carbon) {
+ this.carbon = carbon;
+ }
+
public void setTrueCounterpart(Jid trueCounterpart) {
this.trueCounterpart = trueCounterpart;
}
@@ -416,11 +434,14 @@ public class Message extends AbstractEntity {
}
public String getMergedBody() {
- final Message next = this.next();
- if (this.mergeable(next)) {
- return getBody().trim() + MERGE_SEPARATOR + next.getMergedBody();
+ StringBuilder body = new StringBuilder(this.body.trim());
+ Message current = this;
+ while(current.mergeable(current.next())) {
+ current = current.next();
+ body.append(MERGE_SEPARATOR);
+ body.append(current.getBody().trim());
}
- return getBody().trim();
+ return body.toString();
}
public boolean hasMeCommand() {
@@ -428,20 +449,23 @@ public class Message extends AbstractEntity {
}
public int getMergedStatus() {
- final Message next = this.next();
- if (this.mergeable(next)) {
- return next.getStatus();
+ int status = this.status;
+ Message current = this;
+ while(current.mergeable(current.next())) {
+ current = current.next();
+ status = current.status;
}
- return getStatus();
+ return status;
}
public long getMergedTimeSent() {
- Message next = this.next();
- if (this.mergeable(next)) {
- return next.getMergedTimeSent();
- } else {
- return getTimeSent();
+ long time = this.timeSent;
+ Message current = this;
+ while(current.mergeable(current.next())) {
+ current = current.next();
+ time = current.timeSent;
}
+ return time;
}
public boolean wasMergedIntoPrevious() {
@@ -683,6 +707,37 @@ public class Message extends AbstractEntity {
public boolean isTrusted() {
return conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint)
- == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED;
+ == XmppAxolotlSession.Trust.TRUSTED;
+ }
+
+ private int getPreviousEncryption() {
+ for (Message iterator = this.prev(); iterator != null; iterator = iterator.prev()){
+ if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) {
+ continue;
+ }
+ return iterator.getEncryption();
+ }
+ return ENCRYPTION_NONE;
+ }
+
+ private int getNextEncryption() {
+ for (Message iterator = this.next(); iterator != null; iterator = iterator.next()){
+ if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) {
+ continue;
+ }
+ return iterator.getEncryption();
+ }
+ return conversation.getNextEncryption();
+ }
+
+ public boolean isValidInSession() {
+ int pastEncryption = this.getPreviousEncryption();
+ int futureEncryption = this.getNextEncryption();
+
+ boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE
+ || futureEncryption == ENCRYPTION_NONE
+ || pastEncryption != futureEncryption;
+
+ return inUnencryptedSession || this.getEncryption() == pastEncryption;
}
}
diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
index 88ff8f46c..898d218e4 100644
--- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
@@ -254,12 +254,15 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file) {
+ public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
packet.setTo(host);
Element request = packet.addChild("request",Xmlns.HTTP_UPLOAD);
request.addChild("filename").setContent(file.getName());
request.addChild("size").setContent(String.valueOf(file.getExpectedSize()));
+ if (mime != null) {
+ request.addChild("content-type", mime);
+ }
return packet;
}
}
diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
index 045916725..a06a0dddb 100644
--- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
@@ -63,7 +63,7 @@ public class MessageGenerator extends AbstractGenerator {
if (axolotlMessage == null) {
return null;
}
- packet.setAxolotlMessage(axolotlMessage.toXml());
+ packet.setAxolotlMessage(axolotlMessage.toElement());
return packet;
}
diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
index 30d9a393a..79e4654be 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
@@ -2,7 +2,6 @@ package eu.siacs.conversations.http;
import android.content.Intent;
import android.net.Uri;
-import android.os.SystemClock;
import android.util.Log;
import java.io.BufferedInputStream;
@@ -21,6 +20,8 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Transferable;
+import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
@@ -83,7 +84,7 @@ public class HttpDownloadConnection implements Transferable {
this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
String reference = mUrl.getRef();
if (reference != null && reference.length() == 96) {
- this.file.setKey(CryptoHelper.hexToBytes(reference));
+ this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference));
}
if ((this.message.getEncryption() == Message.ENCRYPTION_OTR
@@ -187,6 +188,8 @@ public class HttpDownloadConnection implements Transferable {
private boolean interactive = false;
+ private OutputStream os;
+
public FileDownloader(boolean interactive) {
this.interactive = interactive;
}
@@ -199,14 +202,16 @@ public class HttpDownloadConnection implements Transferable {
updateImageBounds();
finish();
} catch (SSLHandshakeException e) {
+ FileBackend.close(os);
changeStatus(STATUS_OFFER);
} catch (IOException e) {
+ FileBackend.close(os);
mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
cancel();
}
}
- private void download() throws SSLHandshakeException, IOException {
+ private void download() throws IOException {
HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
if (connection instanceof HttpsURLConnection) {
mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
@@ -215,10 +220,7 @@ public class HttpDownloadConnection implements Transferable {
BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
file.getParentFile().mkdirs();
file.createNewFile();
- OutputStream os = file.createOutputStream();
- if (os == null) {
- throw new IOException();
- }
+ os = AbstractConnectionManager.createOutputStream(file,true);
long transmitted = 0;
long expected = file.getExpectedSize();
int count = -1;
diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
index c25bf13a2..768915a94 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
@@ -1,7 +1,10 @@
package eu.siacs.conversations.http;
import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.Uri;
import android.util.Log;
+import android.util.Pair;
import java.io.IOException;
import java.io.InputStream;
@@ -18,6 +21,7 @@ import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.UiCallback;
import eu.siacs.conversations.utils.CryptoHelper;
@@ -37,13 +41,15 @@ public class HttpUploadConnection implements Transferable {
private Account account;
private DownloadableFile file;
private Message message;
+ private String mime;
private URL mGetUrl;
private URL mPutUrl;
private byte[] key = null;
private long transmitted = 0;
- private long expected = 1;
+
+ private InputStream mFileInputStream;
public HttpUploadConnection(HttpConnectionManager httpConnectionManager) {
this.mHttpConnectionManager = httpConnectionManager;
@@ -67,7 +73,7 @@ public class HttpUploadConnection implements Transferable {
@Override
public int getProgress() {
- return (int) ((((double) transmitted) / expected) * 100);
+ return (int) ((((double) transmitted) / file.getExpectedSize()) * 100);
}
@Override
@@ -78,28 +84,30 @@ public class HttpUploadConnection implements Transferable {
private void fail() {
mHttpConnectionManager.finishUploadConnection(this);
message.setTransferable(null);
- mXmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED);
+ mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
+ FileBackend.close(mFileInputStream);
}
public void init(Message message, boolean delay) {
this.message = message;
message.setTransferable(this);
- mXmppConnectionService.markMessage(message,Message.STATUS_UNSEND);
+ mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
this.account = message.getConversation().getAccount();
this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
- this.file.setExpectedSize(this.file.getSize());
+ this.mime = this.file.getMimeType();
this.delayed = delay;
-
if (Config.ENCRYPT_ON_HTTP_UPLOADED
|| message.getEncryption() == Message.ENCRYPTION_AXOLOTL
|| message.getEncryption() == Message.ENCRYPTION_OTR) {
this.key = new byte[48];
mXmppConnectionService.getRNG().nextBytes(this.key);
- this.file.setKey(this.key);
+ this.file.setKeyAndIv(this.key);
}
-
+ Pair<InputStream,Integer> pair = AbstractConnectionManager.createInputStream(file,true);
+ this.file.setExpectedSize(pair.second);
+ this.mFileInputStream = pair.first;
Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD);
- IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file);
+ IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file,mime);
mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
@@ -134,7 +142,6 @@ public class HttpUploadConnection implements Transferable {
private void upload() {
OutputStream os = null;
- InputStream is = null;
HttpURLConnection connection = null;
try {
Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString());
@@ -144,30 +151,31 @@ public class HttpUploadConnection implements Transferable {
}
connection.setRequestMethod("PUT");
connection.setFixedLengthStreamingMode((int) file.getExpectedSize());
+ connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime);
connection.setDoOutput(true);
connection.connect();
os = connection.getOutputStream();
- is = file.createInputStream();
transmitted = 0;
- expected = file.getExpectedSize();
int count = -1;
byte[] buffer = new byte[4096];
- while (((count = is.read(buffer)) != -1) && !canceled) {
+ while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) {
transmitted += count;
os.write(buffer, 0, count);
mXmppConnectionService.updateConversationUi();
}
os.flush();
os.close();
- is.close();
+ mFileInputStream.close();
int code = connection.getResponseCode();
if (code == 200 || code == 201) {
Log.d(Config.LOGTAG, "finished uploading file");
- Message.FileParams params = message.getFileParams();
if (key != null) {
mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key));
}
mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl);
+ Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ intent.setData(Uri.fromFile(file));
+ mXmppConnectionService.sendBroadcast(intent);
message.setTransferable(null);
message.setCounterpart(message.getConversation().getJid().toBareJid());
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
@@ -188,16 +196,17 @@ public class HttpUploadConnection implements Transferable {
}
});
} else {
- mXmppConnectionService.resendMessage(message,delayed);
+ mXmppConnectionService.resendMessage(message, delayed);
}
} else {
fail();
}
} catch (IOException e) {
- Log.d(Config.LOGTAG, e.getMessage());
+ e.printStackTrace();
+ Log.d(Config.LOGTAG,"http upload failed "+e.getMessage());
fail();
} finally {
- FileBackend.close(is);
+ FileBackend.close(mFileInputStream);
FileBackend.close(os);
if (connection != null) {
connection.disconnect();
diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
index 6e7b3276d..5786487f2 100644
--- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
@@ -73,11 +73,9 @@ public class MessageParser extends AbstractParser implements
body = otrSession.transformReceiving(body);
SessionStatus status = otrSession.getSessionStatus();
if (body == null && status == SessionStatus.ENCRYPTED) {
- conversation.setNextEncryption(Message.ENCRYPTION_OTR);
mXmppConnectionService.onOtrSessionEstablished(conversation);
return null;
} else if (body == null && status == SessionStatus.FINISHED) {
- conversation.setNextEncryption(Message.ENCRYPTION_NONE);
conversation.resetOtrSession();
mXmppConnectionService.updateConversationUi();
return null;
@@ -101,8 +99,8 @@ public class MessageParser extends AbstractParser implements
private Message parseAxolotlChat(Element axolotlMessage, Jid from, String id, Conversation conversation, int status) {
Message finishedMessage = null;
AxolotlService service = conversation.getAccount().getAxolotlService();
- XmppAxolotlMessage xmppAxolotlMessage = new XmppAxolotlMessage(from.toBareJid(), axolotlMessage);
- XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage);
+ XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid());
+ XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage);
if(plaintextMessage != null) {
finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
finishedMessage.setAxolotlFingerprint(plaintextMessage.getFingerprint());
@@ -202,6 +200,13 @@ public class MessageParser extends AbstractParser implements
if (packet.getType() == MessagePacket.TYPE_ERROR) {
Jid from = packet.getFrom();
if (from != null) {
+ Element error = packet.findChild("error");
+ String text = error == null ? null : error.findChildContent("text");
+ if (text != null) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending message to "+ from+ " failed - " + text);
+ } else if (error != null) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending message to "+ from+ " failed - " + error);
+ }
Message message = mXmppConnectionService.markMessage(account,
from.toBareJid(),
packet.getId(),
@@ -223,6 +228,7 @@ public class MessageParser extends AbstractParser implements
final MessagePacket packet;
Long timestamp = null;
final boolean isForwarded;
+ boolean isCarbon = false;
String serverMsgId = null;
final Element fin = original.findChild("fin", "urn:xmpp:mam:0");
if (fin != null) {
@@ -253,7 +259,8 @@ public class MessageParser extends AbstractParser implements
return;
}
timestamp = f != null ? f.second : null;
- isForwarded = f != null;
+ isCarbon = f != null;
+ isForwarded = isCarbon;
} else {
packet = original;
isForwarded = false;
@@ -265,7 +272,7 @@ public class MessageParser extends AbstractParser implements
final String body = packet.getBody();
final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user");
final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
- final Element axolotlEncrypted = packet.findChild("axolotl_message", AxolotlService.PEP_PREFIX);
+ final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
int status;
final Jid counterpart;
final Jid to = packet.getTo();
@@ -339,6 +346,7 @@ public class MessageParser extends AbstractParser implements
message.setCounterpart(counterpart);
message.setRemoteMsgId(remoteMsgId);
message.setServerMsgId(serverMsgId);
+ message.setCarbon(isCarbon);
message.setTime(timestamp);
message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
if (conversation.getMode() == Conversation.MODE_MULTI) {
diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
index 3120c0085..9fe47512b 100644
--- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -27,6 +27,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
@@ -40,7 +42,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private static DatabaseBackend instance = null;
private static final String DATABASE_NAME = "history";
- private static final int DATABASE_VERSION = 15;
+ private static final int DATABASE_VERSION = 16;
private static String CREATE_CONTATCS_STATEMENT = "create table "
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@@ -55,56 +57,56 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ Contact.JID + ") ON CONFLICT REPLACE);";
private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
- + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME + "("
- + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
- + AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, "
- + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
- + AxolotlService.SQLiteAxolotlStore.ACCOUNT
+ + SQLiteAxolotlStore.PREKEY_TABLENAME + "("
+ + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ + SQLiteAxolotlStore.ID + " INTEGER, "
+ + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ + SQLiteAxolotlStore.ACCOUNT
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
- + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
- + AxolotlService.SQLiteAxolotlStore.ID
+ + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+ + SQLiteAxolotlStore.ID
+ ") ON CONFLICT REPLACE"
+");";
private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE "
- + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
- + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
- + AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, "
- + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
- + AxolotlService.SQLiteAxolotlStore.ACCOUNT
+ + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
+ + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ + SQLiteAxolotlStore.ID + " INTEGER, "
+ + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ + SQLiteAxolotlStore.ACCOUNT
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
- + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
- + AxolotlService.SQLiteAxolotlStore.ID
+ + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+ + SQLiteAxolotlStore.ID
+ ") ON CONFLICT REPLACE"+
");";
private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE "
- + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME + "("
- + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
- + AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, "
- + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
- + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
- + AxolotlService.SQLiteAxolotlStore.ACCOUNT
+ + SQLiteAxolotlStore.SESSION_TABLENAME + "("
+ + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ + SQLiteAxolotlStore.NAME + " TEXT, "
+ + SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
+ + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ + SQLiteAxolotlStore.ACCOUNT
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
- + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
- + AxolotlService.SQLiteAxolotlStore.NAME + ", "
- + AxolotlService.SQLiteAxolotlStore.DEVICE_ID
+ + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+ + SQLiteAxolotlStore.NAME + ", "
+ + SQLiteAxolotlStore.DEVICE_ID
+ ") ON CONFLICT REPLACE"
+");";
private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE "
- + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME + "("
- + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
- + AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, "
- + AxolotlService.SQLiteAxolotlStore.OWN + " INTEGER, "
- + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
- + AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, "
- + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
- + AxolotlService.SQLiteAxolotlStore.ACCOUNT
+ + SQLiteAxolotlStore.IDENTITIES_TABLENAME + "("
+ + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ + SQLiteAxolotlStore.NAME + " TEXT, "
+ + SQLiteAxolotlStore.OWN + " INTEGER, "
+ + SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
+ + SQLiteAxolotlStore.TRUSTED + " INTEGER, "
+ + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ + SQLiteAxolotlStore.ACCOUNT
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
- + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
- + AxolotlService.SQLiteAxolotlStore.NAME + ", "
- + AxolotlService.SQLiteAxolotlStore.FINGERPRINT
+ + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+ + SQLiteAxolotlStore.NAME + ", "
+ + SQLiteAxolotlStore.FINGERPRINT
+ ") ON CONFLICT IGNORE"
+");";
@@ -139,6 +141,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ Message.RELATIVE_FILE_PATH + " TEXT, "
+ Message.SERVER_MSG_ID + " TEXT, "
+ Message.FINGERPRINT + " TEXT, "
+ + Message.CARBON + " INTEGER, "
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
+ Message.CONVERSATION + ") REFERENCES "
+ Conversation.TABLENAME + "(" + Conversation.UUID
@@ -294,6 +297,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
+ Message.FINGERPRINT + " TEXT");
}
+ if (oldVersion < 16 && newVersion >= 16) {
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
+ + Message.CARBON + " INTEGER");
+ }
}
public static synchronized DatabaseBackend getInstance(Context context) {
@@ -567,11 +574,11 @@ public class DatabaseBackend extends SQLiteOpenHelper {
String[] selectionArgs = {account.getUuid(),
contact.getName(),
Integer.toString(contact.getDeviceId())};
- Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
+ Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
columns,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
- + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND "
- + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ",
+ SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ + SQLiteAxolotlStore.NAME + " = ? AND "
+ + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
selectionArgs,
null, null, null);
@@ -584,7 +591,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
if(cursor.getCount() != 0) {
cursor.moveToFirst();
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) {
cursor.close();
throw new AssertionError(e);
@@ -597,19 +604,19 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
List<Integer> devices = new ArrayList<>();
final SQLiteDatabase db = this.getReadableDatabase();
- String[] columns = {AxolotlService.SQLiteAxolotlStore.DEVICE_ID};
+ String[] columns = {SQLiteAxolotlStore.DEVICE_ID};
String[] selectionArgs = {account.getUuid(),
contact.getName()};
- Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
+ Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
columns,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
- + AxolotlService.SQLiteAxolotlStore.NAME + " = ?",
+ SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ + SQLiteAxolotlStore.NAME + " = ?",
selectionArgs,
null, null, null);
while(cursor.moveToNext()) {
devices.add(cursor.getInt(
- cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.DEVICE_ID)));
+ cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID)));
}
cursor.close();
@@ -626,11 +633,11 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) {
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.KEY, Base64.encodeToString(session.serialize(),Base64.DEFAULT));
- values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
- db.insert(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
+ values.put(SQLiteAxolotlStore.NAME, contact.getName());
+ values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
+ values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(),Base64.DEFAULT));
+ values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
+ db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
}
public void deleteSession(Account account, AxolotlAddress contact) {
@@ -638,30 +645,30 @@ public class DatabaseBackend extends SQLiteOpenHelper {
String[] args = {account.getUuid(),
contact.getName(),
Integer.toString(contact.getDeviceId())};
- db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
- + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND "
- + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ",
+ db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ + SQLiteAxolotlStore.NAME + " = ? AND "
+ + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
args);
}
public void deleteAllSessions(Account account, AxolotlAddress contact) {
SQLiteDatabase db = this.getWritableDatabase();
String[] args = {account.getUuid(), contact.getName()};
- db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
- + AxolotlService.SQLiteAxolotlStore.NAME + " = ?",
+ db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + "=? AND "
+ + SQLiteAxolotlStore.NAME + " = ?",
args);
}
private Cursor getCursorForPreKey(Account account, int preKeyId) {
SQLiteDatabase db = this.getReadableDatabase();
- String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
+ String[] columns = {SQLiteAxolotlStore.KEY};
String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
- Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME,
+ Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME,
columns,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
- + AxolotlService.SQLiteAxolotlStore.ID + "=?",
+ SQLiteAxolotlStore.ACCOUNT + "=? AND "
+ + SQLiteAxolotlStore.ID + "=?",
selectionArgs,
null, null, null);
@@ -674,7 +681,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
if(cursor.getCount() != 0) {
cursor.moveToFirst();
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 ) {
throw new AssertionError(e);
}
@@ -693,28 +700,28 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void storePreKey(Account account, PreKeyRecord record) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
- values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId());
- values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
- values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
- db.insert(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
+ values.put(SQLiteAxolotlStore.ID, record.getId());
+ values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
+ values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
+ db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
}
public void deletePreKey(Account account, int preKeyId) {
SQLiteDatabase db = this.getWritableDatabase();
String[] args = {account.getUuid(), Integer.toString(preKeyId)};
- db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
- + AxolotlService.SQLiteAxolotlStore.ID + "=?",
+ db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + "=? AND "
+ + SQLiteAxolotlStore.ID + "=?",
args);
}
private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
SQLiteDatabase db = this.getReadableDatabase();
- String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
+ String[] columns = {SQLiteAxolotlStore.KEY};
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,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " + AxolotlService.SQLiteAxolotlStore.ID + "=?",
+ SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?",
selectionArgs,
null, null, null);
@@ -727,7 +734,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
if(cursor.getCount() != 0) {
cursor.moveToFirst();
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 ) {
throw new AssertionError(e);
}
@@ -739,17 +746,17 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
List<SignedPreKeyRecord> prekeys = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
- String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
+ String[] columns = {SQLiteAxolotlStore.KEY};
String[] selectionArgs = {account.getUuid()};
- Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
+ Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
columns,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=?",
+ SQLiteAxolotlStore.ACCOUNT + "=?",
selectionArgs,
null, null, null);
while(cursor.moveToNext()) {
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) {
}
}
@@ -767,18 +774,18 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void storeSignedPreKey(Account account, SignedPreKeyRecord record) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
- values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId());
- values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
- values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
- db.insert(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
+ values.put(SQLiteAxolotlStore.ID, record.getId());
+ values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
+ values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
+ db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
}
public void deleteSignedPreKey(Account account, int signedPreKeyId) {
SQLiteDatabase db = this.getWritableDatabase();
String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)};
- db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
- + AxolotlService.SQLiteAxolotlStore.ID + "=?",
+ db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + "=? AND "
+ + SQLiteAxolotlStore.ID + "=?",
args);
}
@@ -792,24 +799,24 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private Cursor getIdentityKeyCursor(Account account, String name, Boolean own, String fingerprint) {
final SQLiteDatabase db = this.getReadableDatabase();
- String[] columns = {AxolotlService.SQLiteAxolotlStore.TRUSTED,
- AxolotlService.SQLiteAxolotlStore.KEY};
+ String[] columns = {SQLiteAxolotlStore.TRUSTED,
+ SQLiteAxolotlStore.KEY};
ArrayList<String> selectionArgs = new ArrayList<>(4);
selectionArgs.add(account.getUuid());
- String selectionString = AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?";
+ String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?";
if (name != null){
selectionArgs.add(name);
- selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.NAME + " = ?";
+ selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?";
}
if (fingerprint != null){
selectionArgs.add(fingerprint);
- selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ?";
+ selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?";
}
if (own != null){
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,
selectionString,
selectionArgs.toArray(new String[selectionArgs.size()]),
@@ -824,7 +831,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
if(cursor.getCount() != 0) {
cursor.moveToFirst();
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) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
}
@@ -838,18 +845,18 @@ public class DatabaseBackend extends SQLiteOpenHelper {
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, XmppAxolotlSession.Trust trust) {
Set<IdentityKey> identityKeys = new HashSet<>();
Cursor cursor = getIdentityKeyCursor(account, name, false);
while(cursor.moveToNext()) {
if ( trust != null &&
- cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED))
+ cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED))
!= trust.getCode()) {
continue;
}
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) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account"+account.getJid().toBareJid()+", address: "+name);
}
@@ -864,55 +871,55 @@ public class DatabaseBackend extends SQLiteOpenHelper {
String[] args = {
account.getUuid(),
name,
- String.valueOf(AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED.getCode())
+ String.valueOf(XmppAxolotlSession.Trust.TRUSTED.getCode())
};
- return DatabaseUtils.queryNumEntries(db, AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?"
- + " AND " + AxolotlService.SQLiteAxolotlStore.NAME + " = ?"
- + " AND " + AxolotlService.SQLiteAxolotlStore.TRUSTED + " = ?",
+ return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + " = ?"
+ + " AND " + SQLiteAxolotlStore.NAME + " = ?"
+ + " AND " + SQLiteAxolotlStore.TRUSTED + " = ?",
args
);
}
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, XmppAxolotlSession.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, XmppAxolotlSession.Trust trusted) {
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);
- values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trusted.getCode());
- db.insert(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
+ values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
+ values.put(SQLiteAxolotlStore.NAME, name);
+ values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0);
+ values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
+ values.put(SQLiteAxolotlStore.KEY, base64Serialized);
+ values.put(SQLiteAxolotlStore.TRUSTED, trusted.getCode());
+ db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
}
- public AxolotlService.SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String fingerprint) {
+ public XmppAxolotlSession.Trust isIdentityKeyTrusted(Account account, String fingerprint) {
Cursor cursor = getIdentityKeyCursor(account, fingerprint);
- AxolotlService.SQLiteAxolotlStore.Trust trust = null;
+ XmppAxolotlSession.Trust trust = null;
if (cursor.getCount() > 0) {
cursor.moveToFirst();
- int trustValue = cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED));
- trust = AxolotlService.SQLiteAxolotlStore.Trust.fromCode(trustValue);
+ int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED));
+ trust = XmppAxolotlSession.Trust.fromCode(trustValue);
}
cursor.close();
return trust;
}
- public boolean setIdentityKeyTrust(Account account, String fingerprint, AxolotlService.SQLiteAxolotlStore.Trust trust) {
+ public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
SQLiteDatabase db = this.getWritableDatabase();
String[] selectionArgs = {
account.getUuid(),
fingerprint
};
ContentValues values = new ContentValues();
- values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trust.getCode());
- int rows = db.update(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
- + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ",
+ values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode());
+ int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
+ SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
selectionArgs);
return rows == 1;
}
@@ -922,7 +929,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
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), XmppAxolotlSession.Trust.TRUSTED);
}
public void recreateAxolotlDb() {
@@ -931,13 +938,13 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void recreateAxolotlDb(SQLiteDatabase db) {
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("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME);
+ db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME);
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("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME);
+ db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME);
db.execSQL(CREATE_IDENTITIES_STATEMENT);
}
@@ -948,17 +955,17 @@ public class DatabaseBackend extends SQLiteOpenHelper {
String[] deleteArgs= {
accountName
};
- db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
+ db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + " = ?",
deleteArgs);
- db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
+ db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + " = ?",
deleteArgs);
- db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
+ db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + " = ?",
deleteArgs);
- db.delete(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME,
- AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
+ db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + " = ?",
deleteArgs);
}
}
diff --git a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
index 676a09c97..b7e7c8d3a 100644
--- a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
@@ -1,5 +1,33 @@
package eu.siacs.conversations.services;
+import android.util.Log;
+import android.util.Pair;
+
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.DownloadableFile;
+
public class AbstractConnectionManager {
protected XmppConnectionService mXmppConnectionService;
@@ -20,4 +48,73 @@ public class AbstractConnectionManager {
return 524288;
}
}
+
+ public static Pair<InputStream,Integer> createInputStream(DownloadableFile file, boolean gcm) {
+ FileInputStream is;
+ int size;
+ try {
+ is = new FileInputStream(file);
+ size = (int) file.getSize();
+ if (file.getKey() == null) {
+ return new Pair<InputStream,Integer>(is,size);
+ }
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ try {
+ if (gcm) {
+ AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
+ cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
+ InputStream cis = new org.bouncycastle.crypto.io.CipherInputStream(is, cipher);
+ return new Pair<>(cis, cipher.getOutputSize(size));
+ } else {
+ IvParameterSpec ips = new IvParameterSpec(file.getIv());
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips);
+ Log.d(Config.LOGTAG, "opening encrypted input stream");
+ return new Pair<InputStream,Integer>(new CipherInputStream(is, cipher),(size / 16 + 1) * 16);
+ }
+ } catch (InvalidKeyException e) {
+ return null;
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ } catch (NoSuchPaddingException e) {
+ return null;
+ } catch (InvalidAlgorithmParameterException e) {
+ return null;
+ }
+ }
+
+ public static OutputStream createOutputStream(DownloadableFile file, boolean gcm) {
+ FileOutputStream os;
+ try {
+ os = new FileOutputStream(file);
+ if (file.getKey() == null) {
+ return os;
+ }
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ try {
+ if (gcm) {
+ AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
+ cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
+ return new org.bouncycastle.crypto.io.CipherOutputStream(os, cipher);
+ } else {
+ IvParameterSpec ips = new IvParameterSpec(file.getIv());
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips);
+ Log.d(Config.LOGTAG, "opening encrypted output stream");
+ return new CipherOutputStream(os, cipher);
+ }
+ } catch (InvalidKeyException e) {
+ return null;
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ } catch (NoSuchPaddingException e) {
+ return null;
+ } catch (InvalidAlgorithmParameterException e) {
+ return null;
+ }
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java
index b99069a64..7dcf2e83a 100644
--- a/src/main/java/eu/siacs/conversations/services/NotificationService.java
+++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java
@@ -330,9 +330,10 @@ public class NotificationService {
private Message getImage(final Iterable<Message> messages) {
for (final Message message : messages) {
- if (message.getType() == Message.TYPE_IMAGE
+ if (message.getType() != Message.TYPE_TEXT
&& message.getTransferable() == null
- && message.getEncryption() != Message.ENCRYPTION_PGP) {
+ && message.getEncryption() != Message.ENCRYPTION_PGP
+ && message.getFileParams().height > 0) {
return message;
}
}
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index fa0a87096..ffe587d69 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -349,7 +349,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void attachLocationToConversation(final Conversation conversation,
final Uri uri,
final UiCallback<Message> callback) {
- int encryption = conversation.getNextEncryption(forceEncryption());
+ int encryption = conversation.getNextEncryption();
if (encryption == Message.ENCRYPTION_PGP) {
encryption = Message.ENCRYPTION_DECRYPTED;
}
@@ -368,12 +368,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
final Uri uri,
final UiCallback<Message> callback) {
final Message message;
- if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
- message = new Message(conversation, "",
- Message.ENCRYPTION_DECRYPTED);
+ if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
+ message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED);
} else {
- message = new Message(conversation, "",
- conversation.getNextEncryption(forceEncryption()));
+ message = new Message(conversation, "", conversation.getNextEncryption());
}
message.setCounterpart(conversation.getNextCounterpart());
message.setType(Message.TYPE_FILE);
@@ -409,12 +407,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void attachImageToConversation(final Conversation conversation,
final Uri uri, final UiCallback<Message> callback) {
final Message message;
- if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
- message = new Message(conversation, "",
- Message.ENCRYPTION_DECRYPTED);
+ if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
+ message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED);
} else {
- message = new Message(conversation, "",
- conversation.getNextEncryption(forceEncryption()));
+ message = new Message(conversation, "",conversation.getNextEncryption());
}
message.setCounterpart(conversation.getNextCounterpart());
message.setType(Message.TYPE_IMAGE);
@@ -424,7 +420,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void run() {
try {
getFileBackend().copyImageToPrivateStorage(message, uri);
- if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
+ if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
getPgpEngine().encrypt(message, callback);
} else {
callback.success(message);
@@ -759,6 +755,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
break;
case Message.ENCRYPTION_AXOLOTL:
+ message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", ""));
if (message.needsUploading()) {
if (account.httpUploadAvailable() || message.fixCounterpart()) {
this.sendFileMessage(message,delay);
@@ -768,8 +765,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} else {
XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message);
if (axolotlMessage == null) {
- account.getAxolotlService().prepareMessage(message,delay);
- message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", ""));
+ account.getAxolotlService().preparePayloadMessage(message, delay);
} else {
packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage);
}
@@ -2533,8 +2529,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
for (final Message msg : messages) {
+ msg.setTime(System.currentTimeMillis());
markMessage(msg, Message.STATUS_WAITING);
- this.resendMessage(msg,true);
+ this.resendMessage(msg,false);
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
index 435293582..02b8962c5 100644
--- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -111,6 +111,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
private LinearLayout keys;
private LinearLayout tags;
private boolean showDynamicTags;
+ private String messageFingerprint;
private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() {
@@ -193,6 +194,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
} catch (final InvalidJidException ignored) {
}
}
+ this.messageFingerprint = getIntent().getStringExtra("fingerprint");
setContentView(R.layout.activity_contact_details);
contactJidTv = (TextView) findViewById(R.id.details_contactjid);
@@ -386,7 +388,8 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
}
for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys(
contact.getAccount(), contact.getJid().toBareJid().toString())) {
- hasKeys |= addFingerprintRow(keys, contact.getAccount(), identityKey);
+ boolean highlight = identityKey.getFingerprint().replaceAll("\\s", "").equals(messageFingerprint);
+ hasKeys |= addFingerprintRow(keys, contact.getAccount(), identityKey, highlight);
}
if (contact.getPgpKeyId() != 0) {
hasKeys = true;
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index 7f7dcd617..e9b361f4f 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -39,7 +39,7 @@ import de.timroes.android.listview.EnhancedListView;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
-import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.Trust;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Blockable;
import eu.siacs.conversations.entities.Contact;
@@ -402,7 +402,7 @@ public class ConversationActivity extends XmppActivity
} else {
menuAdd.setVisible(!isConversationsOverviewHideable());
if (this.getSelectedConversation() != null) {
- if (this.getSelectedConversation().getNextEncryption(forceEncryption()) != Message.ENCRYPTION_NONE) {
+ if (this.getSelectedConversation().getNextEncryption() != Message.ENCRYPTION_NONE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
menuSecure.setIcon(R.drawable.ic_lock_white_24dp);
} else {
@@ -515,7 +515,7 @@ public class ConversationActivity extends XmppActivity
break;
}
final Conversation conversation = getSelectedConversation();
- final int encryption = conversation.getNextEncryption(forceEncryption());
+ final int encryption = conversation.getNextEncryption();
if (encryption == Message.ENCRYPTION_PGP) {
if (hasPgp()) {
if (conversation.getContact().getPgpKeyId() != 0) {
@@ -792,6 +792,7 @@ public class ConversationActivity extends XmppActivity
xmppConnectionService.databaseBackend.updateConversation(conversation);
fragment.updateChatMsgHint();
invalidateOptionsMenu();
+ refreshUi();
return true;
}
});
@@ -803,15 +804,10 @@ public class ConversationActivity extends XmppActivity
if (conversation.getMode() == Conversation.MODE_MULTI) {
otr.setEnabled(false);
axolotl.setEnabled(false);
- } else {
- if (forceEncryption()) {
- none.setVisible(false);
- }
- }
- if (!conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) {
+ } else if (!conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) {
axolotl.setEnabled(false);
}
- switch (conversation.getNextEncryption(forceEncryption())) {
+ switch (conversation.getNextEncryption()) {
case Message.ENCRYPTION_NONE:
none.setChecked(true);
break;
@@ -822,8 +818,7 @@ public class ConversationActivity extends XmppActivity
pgp.setChecked(true);
break;
case Message.ENCRYPTION_AXOLOTL:
- popup.getMenu().findItem(R.id.encryption_choice_axolotl)
- .setChecked(true);
+ axolotl.setChecked(true);
break;
default:
none.setChecked(true);
@@ -836,8 +831,7 @@ public class ConversationActivity extends XmppActivity
protected void muteConversationDialog(final Conversation conversation) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.disable_notifications);
- final int[] durations = getResources().getIntArray(
- R.array.mute_options_durations);
+ final int[] durations = getResources().getIntArray(R.array.mute_options_durations);
builder.setItems(R.array.mute_options_descriptions,
new OnClickListener() {
@@ -1269,10 +1263,6 @@ public class ConversationActivity extends XmppActivity
});
}
- public boolean forceEncryption() {
- return getPreferences().getBoolean("force_encryption", false);
- }
-
public boolean useSendButtonToIndicateStatus() {
return getPreferences().getBoolean("send_button_status", false);
}
@@ -1287,12 +1277,12 @@ public class ConversationActivity extends XmppActivity
protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) {
AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService();
- boolean hasPendingKeys = !axolotlService.getKeysWithTrust(Trust.UNDECIDED,
+ boolean hasPendingKeys = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED,
mSelectedConversation.getContact()).isEmpty()
|| !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty();
boolean hasNoTrustedKeys = axolotlService.getNumTrustedKeys(mSelectedConversation.getContact()) == 0;
if( hasPendingKeys || hasNoTrustedKeys) {
- axolotlService.createSessionsIfNeeded(mSelectedConversation, false);
+ axolotlService.createSessionsIfNeeded(mSelectedConversation);
Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class);
intent.putExtra("contact", mSelectedConversation.getContact().getJid().toBareJid().toString());
intent.putExtra("account", mSelectedConversation.getAccount().getJid().toBareJid().toString());
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 80f63d812..79cb006d1 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -293,23 +293,27 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (body.length() == 0 || this.conversation == null) {
return;
}
- Message message = new Message(conversation, body, conversation.getNextEncryption(activity.forceEncryption()));
+ Message message = new Message(conversation, body, conversation.getNextEncryption());
if (conversation.getMode() == Conversation.MODE_MULTI) {
if (conversation.getNextCounterpart() != null) {
message.setCounterpart(conversation.getNextCounterpart());
message.setType(Message.TYPE_PRIVATE);
}
}
- if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_OTR) {
- sendOtrMessage(message);
- } else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_PGP) {
- sendPgpMessage(message);
- } else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_AXOLOTL) {
- if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) {
- sendAxolotlMessage(message);
- }
- } else {
- sendPlainTextMessage(message);
+ switch (conversation.getNextEncryption()) {
+ case Message.ENCRYPTION_OTR:
+ sendOtrMessage(message);
+ break;
+ case Message.ENCRYPTION_PGP:
+ sendPgpMessage(message);
+ break;
+ case Message.ENCRYPTION_AXOLOTL:
+ if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) {
+ sendAxolotlMessage(message);
+ }
+ break;
+ default:
+ sendPlainTextMessage(message);
}
}
@@ -320,7 +324,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
R.string.send_private_message_to,
conversation.getNextCounterpart().getResourcepart()));
} else {
- switch (conversation.getNextEncryption(activity.forceEncryption())) {
+ switch (conversation.getNextEncryption()) {
case Message.ENCRYPTION_NONE:
mEditMessage
.setHint(getString(R.string.send_plain_text_message));
@@ -392,12 +396,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
highlightInConference(user);
}
} else {
- activity.switchToContactDetails(message.getContact());
+ activity.switchToContactDetails(message.getContact(), message.getAxolotlFingerprint());
}
} else {
Account account = message.getConversation().getAccount();
Intent intent = new Intent(activity, EditAccountActivity.class);
intent.putExtra("jid", account.getJid().toBareJid().toString());
+ intent.putExtra("fingerprint", message.getAxolotlFingerprint());
startActivity(intent);
}
}
@@ -832,7 +837,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
} catch (final NoSuchElementException ignored) {
}
- activity.xmppConnectionService.updateConversationUi();
+ activity.refreshUi();
}
});
}
@@ -1210,11 +1215,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (resultCode == Activity.RESULT_OK) {
if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) {
final String body = mEditMessage.getText().toString();
- Message message = new Message(conversation, body, conversation.getNextEncryption(activity.forceEncryption()));
+ Message message = new Message(conversation, body, conversation.getNextEncryption());
sendAxolotlMessage(message);
} else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) {
int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID);
- activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption(activity.forceEncryption()));
+ activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption());
}
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index f65485b11..cd4f48d0f 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -74,6 +74,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
private Jid jidToEdit;
private Account mAccount;
+ private String messageFingerprint;
private boolean mFetchingAvatar = false;
@@ -388,6 +389,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} catch (final InvalidJidException | NullPointerException ignored) {
this.jidToEdit = null;
}
+ this.messageFingerprint = getIntent().getStringExtra("fingerprint");
if (this.jidToEdit != null) {
this.mRegisterNew.setVisibility(View.GONE);
if (getActionBar() != null) {
@@ -571,7 +573,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
if(ownKey.equals(identityKey)) {
continue;
}
- hasKeys |= addFingerprintRow(keys, mAccount, identityKey);
+ boolean highlight = identityKey.getFingerprint().replaceAll("\\s", "").equals(messageFingerprint);
+ hasKeys |= addFingerprintRow(keys, mAccount, identityKey, highlight);
}
if (hasKeys) {
keysCard.setVisibility(View.VISIBLE);
diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
index 1bf07f3ea..37ddf5906 100644
--- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
@@ -16,7 +16,7 @@ import java.util.Map;
import java.util.Set;
import eu.siacs.conversations.R;
-import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.Trust;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
@@ -118,8 +118,8 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
boolean hasForeignKeys = false;
for(final IdentityKey identityKey : ownKeysToTrust.keySet()) {
hasOwnKeys = true;
- addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey,
- Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false,
+ addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey, false,
+ XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false,
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@@ -134,8 +134,8 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
}
for(final IdentityKey identityKey : foreignKeysToTrust.keySet()) {
hasForeignKeys = true;
- addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey,
- Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false,
+ addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey, false,
+ XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false,
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@@ -171,11 +171,11 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
}
private void getFingerprints(final Account account) {
- Set<IdentityKey> ownKeysSet = account.getAxolotlService().getKeysWithTrust(Trust.UNDECIDED);
- Set<IdentityKey> foreignKeysSet = account.getAxolotlService().getKeysWithTrust(Trust.UNDECIDED, contact);
+ Set<IdentityKey> ownKeysSet = account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED);
+ Set<IdentityKey> foreignKeysSet = account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, contact);
if (hasNoTrustedKeys) {
- ownKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(Trust.UNTRUSTED));
- foreignKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(Trust.UNTRUSTED, contact));
+ ownKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED));
+ foreignKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact));
}
for(final IdentityKey identityKey : ownKeysSet) {
if(!ownKeysToTrust.containsKey(identityKey)) {
@@ -226,12 +226,12 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
for(IdentityKey identityKey:ownKeysToTrust.keySet()) {
contact.getAccount().getAxolotlService().setFingerprintTrust(
identityKey.getFingerprint().replaceAll("\\s", ""),
- Trust.fromBoolean(ownKeysToTrust.get(identityKey)));
+ XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(identityKey)));
}
for(IdentityKey identityKey:foreignKeysToTrust.keySet()) {
contact.getAccount().getAxolotlService().setFingerprintTrust(
identityKey.getFingerprint().replaceAll("\\s", ""),
- Trust.fromBoolean(foreignKeysToTrust.get(identityKey)));
+ XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(identityKey)));
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index 835685107..3a163ba45 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -70,7 +70,7 @@ import java.util.concurrent.RejectedExecutionException;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
-import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
@@ -351,7 +351,7 @@ public abstract class XmppActivity extends Activity {
mPrimaryTextColor = getResources().getColor(R.color.black87);
mSecondaryTextColor = getResources().getColor(R.color.black54);
mTertiaryTextColor = getResources().getColor(R.color.black12);
- mColorRed = getResources().getColor(R.color.red500);
+ mColorRed = getResources().getColor(R.color.red800);
mColorOrange = getResources().getColor(R.color.orange500);
mColorGreen = getResources().getColor(R.color.green500);
mPrimaryColor = getResources().getColor(R.color.green500);
@@ -424,10 +424,15 @@ public abstract class XmppActivity extends Activity {
}
public void switchToContactDetails(Contact contact) {
+ switchToContactDetails(contact, null);
+ }
+
+ public void switchToContactDetails(Contact contact, String messageFingerprint) {
Intent intent = new Intent(this, ContactDetailsActivity.class);
intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
intent.putExtra("account", contact.getAccount().getJid().toBareJid().toString());
intent.putExtra("contact", contact.getJid().toString());
+ intent.putExtra("fingerprint", messageFingerprint);
startActivity(intent);
}
@@ -608,32 +613,25 @@ public abstract class XmppActivity extends Activity {
builder.create().show();
}
- protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) {
+ protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey, boolean highlight) {
final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", "");
- final AxolotlService.SQLiteAxolotlStore.Trust trust = account.getAxolotlService()
+ final XmppAxolotlSession.Trust trust = account.getAxolotlService()
.getFingerprintTrust(fingerprint);
- return addFingerprintRowWithListeners(keys, account, identityKey, trust, true,
+ return addFingerprintRowWithListeners(keys, account, identityKey, highlight, trust, true,
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- if (isChecked != (trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED)) {
- account.getAxolotlService().setFingerprintTrust(fingerprint,
- (isChecked) ? AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED :
- AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED);
- }
- refreshUi();
- xmppConnectionService.updateAccountUi();
- xmppConnectionService.updateConversationUi();
+ account.getAxolotlService().setFingerprintTrust(fingerprint,
+ (isChecked) ? XmppAxolotlSession.Trust.TRUSTED :
+ XmppAxolotlSession.Trust.UNTRUSTED);
}
},
new View.OnClickListener() {
@Override
public void onClick(View v) {
account.getAxolotlService().setFingerprintTrust(fingerprint,
- AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED);
- refreshUi();
- xmppConnectionService.updateAccountUi();
- xmppConnectionService.updateConversationUi();
+ XmppAxolotlSession.Trust.UNTRUSTED);
+ v.setEnabled(true);
}
}
@@ -641,13 +639,14 @@ public abstract class XmppActivity extends Activity {
}
protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
- final IdentityKey identityKey,
- AxolotlService.SQLiteAxolotlStore.Trust trust,
- boolean showTag,
- CompoundButton.OnCheckedChangeListener
- onCheckedChangeListener,
- View.OnClickListener onClickListener) {
- if (trust == AxolotlService.SQLiteAxolotlStore.Trust.COMPROMISED) {
+ final IdentityKey identityKey,
+ boolean highlight,
+ XmppAxolotlSession.Trust trust,
+ boolean showTag,
+ CompoundButton.OnCheckedChangeListener
+ onCheckedChangeListener,
+ View.OnClickListener onClickListener) {
+ if (trust == XmppAxolotlSession.Trust.COMPROMISED) {
return false;
}
View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
@@ -668,7 +667,7 @@ public abstract class XmppActivity extends Activity {
switch (trust) {
case UNTRUSTED:
case TRUSTED:
- trustToggle.setChecked(trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED, false);
+ trustToggle.setChecked(trust == XmppAxolotlSession.Trust.TRUSTED, false);
trustToggle.setEnabled(true);
key.setTextColor(getPrimaryTextColor());
keyType.setTextColor(getSecondaryTextColor());
@@ -679,7 +678,15 @@ public abstract class XmppActivity extends Activity {
key.setTextColor(getPrimaryTextColor());
keyType.setTextColor(getSecondaryTextColor());
break;
- case INACTIVE:
+ case INACTIVE_UNTRUSTED:
+ case INACTIVE_UNDECIDED:
+ trustToggle.setOnClickListener(null);
+ trustToggle.setChecked(false, false);
+ trustToggle.setEnabled(false);
+ key.setTextColor(getTertiaryTextColor());
+ keyType.setTextColor(getTertiaryTextColor());
+ break;
+ case INACTIVE_TRUSTED:
trustToggle.setOnClickListener(null);
trustToggle.setChecked(true, false);
trustToggle.setEnabled(false);
@@ -693,6 +700,12 @@ public abstract class XmppActivity extends Activity {
} else {
keyType.setVisibility(View.GONE);
}
+ if (highlight) {
+ keyType.setTextColor(getResources().getColor(R.color.accent));
+ keyType.setText(getString(R.string.axolotl_fingerprint_selected_message));
+ } else {
+ keyType.setText(getString(R.string.axolotl_fingerprint));
+ }
key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint()));
keys.addView(view);
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
index 083b78d2d..a3bd18eb4 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
@@ -27,7 +27,7 @@ import android.widget.Toast;
import java.util.List;
import eu.siacs.conversations.R;
-import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
@@ -96,19 +96,15 @@ public class MessageAdapter extends ArrayAdapter<Message> {
return this.getItemViewType(getItem(position));
}
- private int getMessageTextColor(Message message) {
- int type = this.getItemViewType(message);
-
+ private int getMessageTextColor(int type, boolean primary) {
if (type == SENT) {
- return activity.getResources().getColor(R.color.black87);
- } else if (type == RECEIVED) {
- return activity.getResources().getColor(R.color.white);
+ return activity.getResources().getColor(primary ? R.color.black87 : R.color.black54);
+ } else {
+ return activity.getResources().getColor(primary ? R.color.white : R.color.white70);
}
-
- return activity.getPrimaryTextColor();
}
- private void displayStatus(ViewHolder viewHolder, Message message) {
+ private void displayStatus(ViewHolder viewHolder, Message message, int type) {
String filesize = null;
String info = null;
boolean error = false;
@@ -163,24 +159,37 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
break;
}
- if (error) {
+ if (error && type == SENT) {
viewHolder.time.setTextColor(activity.getWarningTextColor());
} else {
- viewHolder.time.setTextColor(this.getMessageTextColor(message));
+ viewHolder.time.setTextColor(this.getMessageTextColor(type,false));
}
if (message.getEncryption() == Message.ENCRYPTION_NONE) {
viewHolder.indicator.setVisibility(View.GONE);
} else {
viewHolder.indicator.setVisibility(View.VISIBLE);
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
- AxolotlService.SQLiteAxolotlStore.Trust trust = message.getConversation()
+ XmppAxolotlSession.Trust trust = message.getConversation()
.getAccount().getAxolotlService().getFingerprintTrust(
message.getAxolotlFingerprint());
- if(trust == null || trust != AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED) {
- viewHolder.indicator.setColorFilter(Color.RED);
+ if(trust == null || trust != XmppAxolotlSession.Trust.TRUSTED) {
+ viewHolder.indicator.setColorFilter(activity.getWarningTextColor());
+ viewHolder.indicator.setAlpha(1.0f);
} else {
viewHolder.indicator.clearColorFilter();
+ if (type == SENT) {
+ viewHolder.indicator.setAlpha(0.57f);
+ } else {
+ viewHolder.indicator.setAlpha(0.7f);
+ }
+ }
+ } else {
+ viewHolder.indicator.clearColorFilter();
+ if (type == SENT) {
+ viewHolder.indicator.setAlpha(0.57f);
+ } else {
+ viewHolder.indicator.setAlpha(0.7f);
}
}
}
@@ -214,19 +223,19 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
- private void displayInfoMessage(ViewHolder viewHolder, String text) {
+ private void displayInfoMessage(ViewHolder viewHolder, String text, int type) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
}
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setText(text);
- viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
+ viewHolder.messageBody.setTextColor(getMessageTextColor(type,false));
viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
viewHolder.messageBody.setTextIsSelectable(false);
}
- private void displayDecryptionFailed(ViewHolder viewHolder) {
+ private void displayDecryptionFailed(ViewHolder viewHolder, int type) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
}
@@ -234,7 +243,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setText(getContext().getString(
R.string.decryption_failed));
- viewHolder.messageBody.setTextColor(activity.getWarningTextColor());
+ viewHolder.messageBody.setTextColor(getMessageTextColor(type,false));
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
viewHolder.messageBody.setTextIsSelectable(false);
}
@@ -252,7 +261,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.messageBody.setText(span);
}
- private void displayTextMessage(final ViewHolder viewHolder, final Message message) {
+ private void displayTextMessage(final ViewHolder viewHolder, final Message message, int type) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
}
@@ -310,7 +319,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else {
viewHolder.messageBody.setText("");
}
- viewHolder.messageBody.setTextColor(this.getMessageTextColor(message));
+ viewHolder.messageBody.setTextColor(this.getMessageTextColor(type,true));
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
viewHolder.messageBody.setTextIsSelectable(true);
}
@@ -519,7 +528,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
} else {
- displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first);
+ displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first,type);
}
} else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
displayImageMessage(viewHolder, message);
@@ -531,10 +540,9 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
if (activity.hasPgp()) {
- displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
+ displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message),type);
} else {
- displayInfoMessage(viewHolder,
- activity.getString(R.string.install_openkeychain));
+ displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),type);
if (viewHolder != null) {
viewHolder.message_box
.setOnClickListener(new OnClickListener() {
@@ -547,7 +555,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
- displayDecryptionFailed(viewHolder);
+ displayDecryptionFailed(viewHolder,type);
} else {
if (GeoHelper.isGeoUri(message.getBody())) {
displayLocationMessage(viewHolder,message);
@@ -556,11 +564,19 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else if (message.treatAsDownloadable() == Message.Decision.MUST) {
displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
} else {
- displayTextMessage(viewHolder, message);
+ displayTextMessage(viewHolder, message, type);
+ }
+ }
+
+ if (type == RECEIVED) {
+ if(message.isValidInSession()) {
+ viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received);
+ } else {
+ viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning);
}
}
- displayStatus(viewHolder, message);
+ displayStatus(viewHolder, message, type);
return view;
}
diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
index 36d03b30a..4d0dd3dab 100644
--- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
@@ -38,17 +38,14 @@ public class DNSHelper {
public static Bundle getSRVRecord(final Jid jid) throws IOException {
final String host = jid.getDomainpart();
String dns[] = client.findDNS();
-
- if (dns != null) {
- for (String dnsserver : dns) {
- InetAddress ip = InetAddress.getByName(dnsserver);
- Bundle b = queryDNS(host, ip);
- if (b.containsKey("values")) {
- return b;
- }
+ for (int i = 0; i < dns.length; ++i) {
+ InetAddress ip = InetAddress.getByName(dns[i]);
+ Bundle b = queryDNS(host, ip);
+ if (b.containsKey("values") || i == dns.length - 1) {
+ return b;
}
}
- return queryDNS(host, InetAddress.getByName("8.8.8.8"));
+ return null;
}
public static Bundle queryDNS(String host, InetAddress dnsServer) {
@@ -132,7 +129,6 @@ public class DNSHelper {
} catch (SocketTimeoutException e) {
bundle.putString("error", "timeout");
} catch (Exception e) {
- e.printStackTrace();
bundle.putString("error", "unhandled");
}
return bundle;
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index 7c81d988c..c41c5174e 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -164,6 +164,9 @@ public class XmppConnection implements Runnable {
}
} else {
final Bundle result = DNSHelper.getSRVRecord(account.getServer());
+ if (result == null) {
+ throw new IOException("unhandled exception in DNS resolver");
+ }
final ArrayList<Parcelable> values = result.getParcelableArrayList("values");
if ("timeout".equals(result.getString("error"))) {
throw new IOException("timeout in dns");
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
index e15fc5224..e9ca66b7c 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
@@ -2,9 +2,11 @@ package eu.siacs.conversations.xmpp.jingle;
import android.content.Intent;
import android.net.Uri;
-import android.os.SystemClock;
import android.util.Log;
+import android.util.Pair;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
@@ -14,13 +16,19 @@ import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import eu.siacs.conversations.Config;
+import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.OnMessageCreatedCallback;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.TransferablePlaceholder;
+import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -66,8 +74,13 @@ public class JingleConnection implements Transferable {
private boolean acceptedAutomatically = false;
+ private XmppAxolotlMessage mXmppAxolotlMessage;
+
private JingleTransport transport = null;
+ private OutputStream mFileOutputStream;
+ private InputStream mFileInputStream;
+
private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
@Override
@@ -113,6 +126,14 @@ public class JingleConnection implements Transferable {
}
};
+ public InputStream getFileInputStream() {
+ return this.mFileInputStream;
+ }
+
+ public OutputStream getFileOutputStream() {
+ return this.mFileOutputStream;
+ }
+
private OnProxyActivated onProxyActivated = new OnProxyActivated() {
@Override
@@ -194,7 +215,22 @@ public class JingleConnection implements Transferable {
mXmppConnectionService.sendIqPacket(account,response,null);
}
- public void init(Message message) {
+ public void init(final Message message) {
+ if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
+ Conversation conversation = message.getConversation();
+ conversation.getAccount().getAxolotlService().prepareKeyTransportMessage(conversation.getContact(), new OnMessageCreatedCallback() {
+ @Override
+ public void run(XmppAxolotlMessage xmppAxolotlMessage) {
+ init(message, xmppAxolotlMessage);
+ }
+ });
+ } else {
+ init(message, null);
+ }
+ }
+
+ private void init(Message message, XmppAxolotlMessage xmppAxolotlMessage) {
+ this.mXmppAxolotlMessage = xmppAxolotlMessage;
this.contentCreator = "initiator";
this.contentName = this.mJingleConnectionManager.nextRandomId();
this.message = message;
@@ -238,8 +274,7 @@ public class JingleConnection implements Transferable {
});
mergeCandidate(candidate);
} else {
- Log.d(Config.LOGTAG,
- "no primary candidate of our own was found");
+ Log.d(Config.LOGTAG,"no primary candidate of our own was found");
sendInitRequest();
}
}
@@ -267,13 +302,16 @@ public class JingleConnection implements Transferable {
this.contentCreator = content.getAttribute("creator");
this.contentName = content.getAttribute("name");
this.transportId = content.getTransportId();
- this.mergeCandidates(JingleCandidate.parse(content.socks5transport()
- .getChildren()));
+ this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren()));
this.fileOffer = packet.getJingleContent().getFileOffer();
mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null);
if (fileOffer != null) {
+ Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX);
+ if (encrypted != null) {
+ this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().toBareJid());
+ }
Element fileSize = fileOffer.findChild("size");
Element fileNameElement = fileOffer.findChild("name");
if (fileNameElement != null) {
@@ -319,10 +357,8 @@ public class JingleConnection implements Transferable {
message.setBody(Long.toString(size));
conversation.add(message);
mXmppConnectionService.updateConversationUi();
- if (size < this.mJingleConnectionManager
- .getAutoAcceptFileSize()) {
- Log.d(Config.LOGTAG, "auto accepting file from "
- + packet.getFrom());
+ if (size < this.mJingleConnectionManager.getAutoAcceptFileSize()) {
+ Log.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom());
this.acceptedAutomatically = true;
this.sendAccept();
} else {
@@ -333,22 +369,32 @@ public class JingleConnection implements Transferable {
+ " allowed size:"
+ this.mJingleConnectionManager
.getAutoAcceptFileSize());
- this.mXmppConnectionService.getNotificationService()
- .push(message);
+ this.mXmppConnectionService.getNotificationService().push(message);
}
- this.file = this.mXmppConnectionService.getFileBackend()
- .getFile(message, false);
- if (message.getEncryption() == Message.ENCRYPTION_OTR) {
+ this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
+ if (mXmppAxolotlMessage != null) {
+ XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage);
+ if (transportMessage != null) {
+ message.setEncryption(Message.ENCRYPTION_AXOLOTL);
+ this.file.setKey(transportMessage.getKey());
+ this.file.setIv(transportMessage.getIv());
+ message.setAxolotlFingerprint(transportMessage.getFingerprint());
+ } else {
+ Log.d(Config.LOGTAG,"could not process KeyTransportMessage");
+ }
+ } else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
byte[] key = conversation.getSymmetricKey();
if (key == null) {
this.sendCancel();
this.fail();
return;
} else {
- this.file.setKey(key);
+ this.file.setKeyAndIv(key);
}
}
+ this.mFileOutputStream = AbstractConnectionManager.createOutputStream(this.file,message.getEncryption() == Message.ENCRYPTION_AXOLOTL);
this.file.setExpectedSize(size);
+ Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize());
} else {
this.sendCancel();
this.fail();
@@ -364,19 +410,30 @@ public class JingleConnection implements Transferable {
Content content = new Content(this.contentCreator, this.contentName);
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
content.setTransportId(this.transportId);
- this.file = this.mXmppConnectionService.getFileBackend().getFile(
- message, false);
+ this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
+ Pair<InputStream,Integer> pair;
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
Conversation conversation = this.message.getConversation();
if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key");
cancel();
}
+ this.file.setKeyAndIv(conversation.getSymmetricKey());
+ pair = AbstractConnectionManager.createInputStream(this.file,false);
+ this.file.setExpectedSize(pair.second);
content.setFileOffer(this.file, true);
- this.file.setKey(conversation.getSymmetricKey());
+ } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
+ this.file.setKey(mXmppAxolotlMessage.getInnerKey());
+ this.file.setIv(mXmppAxolotlMessage.getIV());
+ pair = AbstractConnectionManager.createInputStream(this.file,true);
+ this.file.setExpectedSize(pair.second);
+ content.setFileOffer(this.file, false).addChild(mXmppAxolotlMessage.toElement());
} else {
+ pair = AbstractConnectionManager.createInputStream(this.file,false);
+ this.file.setExpectedSize(pair.second);
content.setFileOffer(this.file, false);
}
+ this.mFileInputStream = pair.first;
this.transportId = this.mJingleConnectionManager.nextRandomId();
content.setTransportId(this.transportId);
content.socks5transport().setChildren(getCandidatesAsElements());
@@ -748,6 +805,8 @@ public class JingleConnection implements Transferable {
if (this.transport != null && this.transport instanceof JingleInbandTransport) {
this.transport.disconnect();
}
+ FileBackend.close(mFileInputStream);
+ FileBackend.close(mFileOutputStream);
if (this.message != null) {
if (this.responder.equals(account.getJid())) {
this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
index 6f31f1639..ab7ab73b8 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
@@ -93,7 +93,7 @@ public class JingleInbandTransport extends JingleTransport {
digest.reset();
file.getParentFile().mkdirs();
file.createNewFile();
- this.fileOutputStream = file.createOutputStream();
+ this.fileOutputStream = connection.getFileOutputStream();
if (this.fileOutputStream == null) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream");
callback.onFileTransferAborted();
@@ -112,15 +112,11 @@ public class JingleInbandTransport extends JingleTransport {
this.onFileTransmissionStatusChanged = callback;
this.file = file;
try {
- if (this.file.getKey() != null) {
- this.remainingSize = (this.file.getSize() / 16 + 1) * 16;
- } else {
- this.remainingSize = this.file.getSize();
- }
+ this.remainingSize = this.file.getExpectedSize();
this.fileSize = this.remainingSize;
this.digest = MessageDigest.getInstance("SHA-1");
this.digest.reset();
- fileInputStream = this.file.createInputStream();
+ fileInputStream = connection.getFileInputStream();
if (fileInputStream == null) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream");
callback.onFileTransferAborted();
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
index 8d74f44e3..7545dd646 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
@@ -106,13 +106,13 @@ public class JingleSocks5Transport extends JingleTransport {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
- fileInputStream = file.createInputStream();
+ fileInputStream = connection.getFileInputStream();
if (fileInputStream == null) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
callback.onFileTransferAborted();
return;
}
- long size = file.getSize();
+ long size = file.getExpectedSize();
long transmitted = 0;
int count;
byte[] buffer = new byte[8192];
@@ -157,7 +157,7 @@ public class JingleSocks5Transport extends JingleTransport {
socket.setSoTimeout(30000);
file.getParentFile().mkdirs();
file.createNewFile();
- fileOutputStream = file.createOutputStream();
+ fileOutputStream = connection.getFileOutputStream();
if (fileOutputStream == null) {
callback.onFileTransferAborted();
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java
index e832d3f58..b32111584 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java
@@ -1,5 +1,31 @@
package eu.siacs.conversations.xmpp.jingle;
+import android.util.Log;
+import android.util.Pair;
+
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.DownloadableFile;
public abstract class JingleTransport {
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
index bcadbe778..f752cc5d3 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
@@ -25,17 +25,18 @@ public class Content extends Element {
this.transportId = sid;
}
- public void setFileOffer(DownloadableFile actualFile, boolean otr) {
+ public Element setFileOffer(DownloadableFile actualFile, boolean otr) {
Element description = this.addChild("description",
"urn:xmpp:jingle:apps:file-transfer:3");
Element offer = description.addChild("offer");
Element file = offer.addChild("file");
- file.addChild("size").setContent(Long.toString(actualFile.getSize()));
+ file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize()));
if (otr) {
file.addChild("name").setContent(actualFile.getName() + ".otr");
} else {
file.addChild("name").setContent(actualFile.getName());
}
+ return file;
}
public Element getFileOffer() {
diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java
index 7b36fc497..398102e12 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java
@@ -39,6 +39,9 @@ public class IqPacket extends AbstractStanza {
public TYPE getType() {
final String type = getAttribute("type");
+ if (type == null) {
+ return TYPE.INVALID;
+ }
switch (type) {
case "error":
return TYPE.ERROR;