diff --git a/app/src/androidTest/java/im/conversations/android/xmpp/MessageTransformationTest.java b/app/src/androidTest/java/im/conversations/android/xmpp/MessageTransformationTest.java index eab2b61c5..4e6a756e7 100644 --- a/app/src/androidTest/java/im/conversations/android/xmpp/MessageTransformationTest.java +++ b/app/src/androidTest/java/im/conversations/android/xmpp/MessageTransformationTest.java @@ -54,7 +54,7 @@ public class MessageTransformationTest { final long id = database.accountDao().insert(account); this.transformer = - new Transformer(database, database.accountDao().getEnabledAccount(id).get()); + new Transformer(database.accountDao().getEnabledAccount(id).get(), database); } @Test diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4052c7f85..7b3d13f49 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -79,9 +79,12 @@ + android:exported="false"/> - + + android:windowSoftInputMode="adjustResize"> - + diff --git a/app/src/main/java/im/conversations/android/AbstractAccountService.java b/app/src/main/java/im/conversations/android/AbstractAccountService.java index 72b285537..afbabe14d 100644 --- a/app/src/main/java/im/conversations/android/AbstractAccountService.java +++ b/app/src/main/java/im/conversations/android/AbstractAccountService.java @@ -1,15 +1,15 @@ package im.conversations.android; -import android.content.Context; +import im.conversations.android.database.ConversationsDatabase; import im.conversations.android.database.model.Account; public abstract class AbstractAccountService { - protected Context context; protected Account account; + protected ConversationsDatabase database; - protected AbstractAccountService(final Context context, final Account account) { - this.context = context; + protected AbstractAccountService(final Account account, final ConversationsDatabase database) { this.account = account; + this.database = database; } } diff --git a/app/src/main/java/im/conversations/android/axolotl/AxolotlPayload.java b/app/src/main/java/im/conversations/android/axolotl/AxolotlPayload.java index a3db568ed..48a767410 100644 --- a/app/src/main/java/im/conversations/android/axolotl/AxolotlPayload.java +++ b/app/src/main/java/im/conversations/android/axolotl/AxolotlPayload.java @@ -24,4 +24,8 @@ public class AxolotlPayload { public String payloadAsString() { return new String(payload, StandardCharsets.UTF_8); } + + public boolean hasPayload() { + return payload != null; + } } diff --git a/app/src/main/java/im/conversations/android/axolotl/AxolotlService.java b/app/src/main/java/im/conversations/android/axolotl/AxolotlService.java index aaae7f52d..f8426f42e 100644 --- a/app/src/main/java/im/conversations/android/axolotl/AxolotlService.java +++ b/app/src/main/java/im/conversations/android/axolotl/AxolotlService.java @@ -1,11 +1,11 @@ package im.conversations.android.axolotl; -import android.content.Context; import android.os.Build; import com.google.common.base.Optional; import eu.siacs.conversations.xmpp.jingle.OmemoVerification; import im.conversations.android.AbstractAccountService; import im.conversations.android.database.AxolotlDatabaseStore; +import im.conversations.android.database.ConversationsDatabase; import im.conversations.android.database.model.Account; import im.conversations.android.xmpp.model.axolotl.Encrypted; import im.conversations.android.xmpp.model.axolotl.Header; @@ -22,6 +22,8 @@ import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.jxmpp.jid.Jid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.DuplicateMessageException; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.InvalidKeyException; @@ -38,6 +40,8 @@ import org.whispersystems.libsignal.state.SignalProtocolStore; public class AxolotlService extends AbstractAccountService { + private static final Logger LOGGER = LoggerFactory.getLogger(AxolotlService.class); + public static final String KEY_TYPE = "AES"; public static final String CIPHER_MODE = "AES/GCM/NoPadding"; @@ -45,9 +49,10 @@ public class AxolotlService extends AbstractAccountService { private final SignalProtocolStore signalProtocolStore; - public AxolotlService(final Context context, final Account account) { - super(context, account); - this.signalProtocolStore = new AxolotlDatabaseStore(context, account); + public AxolotlService( + final Account account, final ConversationsDatabase conversationsDatabase) { + super(account, conversationsDatabase); + this.signalProtocolStore = new AxolotlDatabaseStore(account, conversationsDatabase); } private AxolotlSession buildReceivingSession( @@ -119,6 +124,10 @@ public class AxolotlService extends AbstractAccountService { final Header header = encrypted.getHeader(); final Key ourKey = header.getKey(signalProtocolStore.getLocalRegistrationId()); if (ourKey == null) { + LOGGER.info( + "looking for {} in {}", + signalProtocolStore.getLocalRegistrationId(), + header.getKeys()); throw new NotEncryptedForThisDeviceException(); } final byte[] keyWithAuthTag; diff --git a/app/src/main/java/im/conversations/android/database/AxolotlDatabaseStore.java b/app/src/main/java/im/conversations/android/database/AxolotlDatabaseStore.java index 6c7dd8e7b..fd467d5b8 100644 --- a/app/src/main/java/im/conversations/android/database/AxolotlDatabaseStore.java +++ b/app/src/main/java/im/conversations/android/database/AxolotlDatabaseStore.java @@ -1,6 +1,5 @@ package im.conversations.android.database; -import android.content.Context; import im.conversations.android.AbstractAccountService; import im.conversations.android.axolotl.AxolotlAddress; import im.conversations.android.database.dao.AxolotlDao; @@ -17,12 +16,13 @@ import org.whispersystems.libsignal.state.SignedPreKeyRecord; public class AxolotlDatabaseStore extends AbstractAccountService implements SignalProtocolStore { - public AxolotlDatabaseStore(final Context context, final Account account) { - super(context, account); + public AxolotlDatabaseStore( + final Account account, final ConversationsDatabase conversationsDatabase) { + super(account, conversationsDatabase); } private AxolotlDao axolotlDao() { - return ConversationsDatabase.getInstance(context).axolotlDao(); + return database.axolotlDao(); } @Override diff --git a/app/src/main/java/im/conversations/android/tls/TrustManager.java b/app/src/main/java/im/conversations/android/tls/TrustManager.java index cf220cf5a..46fac9cb7 100644 --- a/app/src/main/java/im/conversations/android/tls/TrustManager.java +++ b/app/src/main/java/im/conversations/android/tls/TrustManager.java @@ -1,16 +1,19 @@ package im.conversations.android.tls; import android.content.Context; -import im.conversations.android.AbstractAccountService; import im.conversations.android.database.model.Account; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.X509TrustManager; -public class TrustManager extends AbstractAccountService implements X509TrustManager { +public class TrustManager implements X509TrustManager { + + private final Context context; + private final Account account; public TrustManager(final Context context, final Account account) { - super(context, account); + this.context = context; + this.account = account; } @Override diff --git a/app/src/main/java/im/conversations/android/transformer/Transformer.java b/app/src/main/java/im/conversations/android/transformer/Transformer.java index c37c433f3..421e06b0c 100644 --- a/app/src/main/java/im/conversations/android/transformer/Transformer.java +++ b/app/src/main/java/im/conversations/android/transformer/Transformer.java @@ -4,6 +4,8 @@ import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import im.conversations.android.axolotl.AxolotlDecryptionException; +import im.conversations.android.axolotl.AxolotlService; import im.conversations.android.database.ConversationsDatabase; import im.conversations.android.database.model.Account; import im.conversations.android.database.model.ChatIdentifier; @@ -36,10 +38,20 @@ public class Transformer { private final ConversationsDatabase database; private final Account account; - public Transformer(final ConversationsDatabase database, final Account account) { + private final AxolotlService axolotlService; + + public Transformer(final Account account, final ConversationsDatabase conversationsDatabase) { + this(account, conversationsDatabase, new AxolotlService(account, conversationsDatabase)); + } + + public Transformer( + final Account account, + final ConversationsDatabase database, + final AxolotlService axolotlService) { Preconditions.checkArgument(account != null, "Account must not be null"); this.database = database; this.account = account; + this.axolotlService = axolotlService; } public boolean transform(final MessageTransformation transformation) { @@ -72,11 +84,30 @@ public class Transformer { chat, transformation.messageId, MessageState.error(transformation)); return false; } + final Replace messageCorrection = transformation.getExtension(Replace.class); final Reactions reactions = transformation.getExtension(Reactions.class); final Retract retract = transformation.getExtension(Retract.class); - // TODO we need to remove fallbacks for reactions, retractions and potentially other things - final List contents = parseContent(transformation); + final Encrypted encrypted = transformation.getExtension(Encrypted.class); + final List contents; + if (encrypted != null) { + try { + final var payload = axolotlService.decrypt(transformation.from, encrypted); + if (payload.hasPayload()) { + contents = ImmutableList.of(MessageContent.text(payload.payloadAsString(),null)); + } else { + return true; + } + } catch (final AxolotlDecryptionException e) { + LOGGER.error("Could not decrypt message", e); + // TODO if message had payload create error message entry + return false; + } + } else { + // TODO we need to remove fallbacks for reactions, retractions and potentially other + // things + contents = parseContent(transformation); + } final boolean identifiableSender = Arrays.asList(Message.Type.NORMAL, Message.Type.CHAT).contains(messageType) diff --git a/app/src/main/java/im/conversations/android/xmpp/XmppConnection.java b/app/src/main/java/im/conversations/android/xmpp/XmppConnection.java index 6a5b74e1c..223c9e4ea 100644 --- a/app/src/main/java/im/conversations/android/xmpp/XmppConnection.java +++ b/app/src/main/java/im/conversations/android/xmpp/XmppConnection.java @@ -17,7 +17,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; -import im.conversations.android.AbstractAccountService; import im.conversations.android.BuildConfig; import im.conversations.android.Conversations; import im.conversations.android.IDs; @@ -106,13 +105,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmlpull.v1.XmlPullParserException; -public class XmppConnection extends AbstractAccountService implements Runnable { +public class XmppConnection implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(XmppConnection.class); private static final boolean EXTENDED_SM_LOGGING = false; private static final int CONNECT_DISCO_TIMEOUT = 20; + private final Context context; + private final Account account; + private final SparseArray mStanzaQueue = new SparseArray<>(); private final Hashtable>> packetCallbacks = new Hashtable<>(); private Socket socket; @@ -155,7 +157,8 @@ public class XmppConnection extends AbstractAccountService implements Runnable { private CountDownLatch mStreamCountDownLatch; public XmppConnection(final Context context, final Account account) { - super(context, account); + this.context = context; + this.account = account; this.connectionAddress = account.address; // these consumers are pure listeners; they don’t have public method except for accept|apply diff --git a/app/src/main/java/im/conversations/android/xmpp/manager/AxolotlManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/AxolotlManager.java index 5bfbcbc25..e152673e2 100644 --- a/app/src/main/java/im/conversations/android/xmpp/manager/AxolotlManager.java +++ b/app/src/main/java/im/conversations/android/xmpp/manager/AxolotlManager.java @@ -22,6 +22,7 @@ import im.conversations.android.axolotl.AxolotlPayload; import im.conversations.android.axolotl.AxolotlService; import im.conversations.android.axolotl.AxolotlSession; import im.conversations.android.axolotl.EncryptionBuilder; +import im.conversations.android.database.ConversationsDatabase; import im.conversations.android.xml.Element; import im.conversations.android.xml.Namespace; import im.conversations.android.xmpp.IqErrorException; @@ -60,7 +61,13 @@ public class AxolotlManager extends AbstractManager { public AxolotlManager(Context context, XmppConnection connection) { super(context, connection); - this.axolotlService = new AxolotlService(context, connection.getAccount()); + this.axolotlService = + new AxolotlService( + connection.getAccount(), ConversationsDatabase.getInstance(context)); + } + + public AxolotlService getAxolotlService() { + return this.axolotlService; } public void handleItems(final BareJid from, final Items items) { @@ -286,8 +293,11 @@ public class AxolotlManager extends AbstractManager { throw new IllegalStateException("No signed PreKeys have been created yet"); } bundle.setSignedPreKey( - signedPreKeyRecord.getKeyPair().getPublicKey(), signedPreKeyRecord.getSignature()); - bundle.setPreKeys(getDatabase().axolotlDao().getPreKeys(getAccount().id)); + signedPreKeyRecord.getId(), + signedPreKeyRecord.getKeyPair().getPublicKey(), + signedPreKeyRecord.getSignature()); + bundle.addPreKeys(getDatabase().axolotlDao().getPreKeys(getAccount().id)); + LOGGER.info("bundle {}", bundle); return bundle; } diff --git a/app/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java index ce0dd5da7..2321c2e49 100644 --- a/app/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java +++ b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java @@ -40,14 +40,16 @@ public class Bundle extends Extension { identityKey.setContent(ecPublicKey); } - public void setSignedPreKey(final ECPublicKey ecPublicKey, final byte[] signature) { + public void setSignedPreKey( + final int id, final ECPublicKey ecPublicKey, final byte[] signature) { final var signedPreKey = this.addExtension(new SignedPreKey()); + signedPreKey.setId(id); signedPreKey.setContent(ecPublicKey); final var signedPreKeySignature = this.addExtension(new SignedPreKeySignature()); signedPreKeySignature.setContent(signature); } - public void setPreKeys(final List preKeyRecords) { + public void addPreKeys(final List preKeyRecords) { final var preKeys = this.addExtension(new PreKeys()); for (final PreKeyRecord preKeyRecord : preKeyRecords) { final var preKey = preKeys.addExtension(new PreKey()); diff --git a/app/src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java index 93e368a00..22164976a 100644 --- a/app/src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java +++ b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java @@ -4,7 +4,7 @@ import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.ByteContent; import im.conversations.android.xmpp.model.Extension; -@XmlElement +@XmlElement(name = "iv") public class IV extends Extension implements ByteContent { public IV() { diff --git a/app/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java index b3c0eb35c..a7d39c1da 100644 --- a/app/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java +++ b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java @@ -16,6 +16,6 @@ public class PreKey extends Extension implements ECPublicKeyContent { } public void setId(int id) { - this.setAttribute("id", id); + this.setAttribute("preKeyId", id); } } diff --git a/app/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java index 2db2c3e35..0e0ca7282 100644 --- a/app/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java +++ b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java @@ -14,4 +14,8 @@ public class SignedPreKey extends Extension implements ECPublicKeyContent { public int getId() { return Ints.saturatedCast(this.getLongAttribute("signedPreKeyId")); } + + public void setId(final int id) { + this.setAttribute("signedPreKeyId", id); + } } diff --git a/app/src/main/java/im/conversations/android/xmpp/model/state/Active.java b/app/src/main/java/im/conversations/android/xmpp/model/state/Active.java index 8053b014b..15970bc5b 100644 --- a/app/src/main/java/im/conversations/android/xmpp/model/state/Active.java +++ b/app/src/main/java/im/conversations/android/xmpp/model/state/Active.java @@ -5,7 +5,7 @@ import im.conversations.android.annotation.XmlElement; @XmlElement public class Active extends ChatStateNotification { - protected Active() { + public Active() { super(Active.class); } } diff --git a/app/src/main/java/im/conversations/android/xmpp/model/state/Composing.java b/app/src/main/java/im/conversations/android/xmpp/model/state/Composing.java index e85dd9e5b..9871952e0 100644 --- a/app/src/main/java/im/conversations/android/xmpp/model/state/Composing.java +++ b/app/src/main/java/im/conversations/android/xmpp/model/state/Composing.java @@ -5,7 +5,7 @@ import im.conversations.android.annotation.XmlElement; @XmlElement public class Composing extends ChatStateNotification { - protected Composing() { + public Composing() { super(Composing.class); } } diff --git a/app/src/main/java/im/conversations/android/xmpp/model/state/Gone.java b/app/src/main/java/im/conversations/android/xmpp/model/state/Gone.java index 41b5a3fb6..a0a74e788 100644 --- a/app/src/main/java/im/conversations/android/xmpp/model/state/Gone.java +++ b/app/src/main/java/im/conversations/android/xmpp/model/state/Gone.java @@ -5,7 +5,7 @@ import im.conversations.android.annotation.XmlElement; @XmlElement public class Gone extends ChatStateNotification { - protected Gone() { + public Gone() { super(Gone.class); } } diff --git a/app/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java b/app/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java index a20b6f670..4a3670308 100644 --- a/app/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java +++ b/app/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java @@ -5,7 +5,7 @@ import im.conversations.android.annotation.XmlElement; @XmlElement public class Inactive extends ChatStateNotification { - protected Inactive() { + public Inactive() { super(Inactive.class); } } diff --git a/app/src/main/java/im/conversations/android/xmpp/processor/MessageProcessor.java b/app/src/main/java/im/conversations/android/xmpp/processor/MessageProcessor.java index 7e60fd254..eebf86c5b 100644 --- a/app/src/main/java/im/conversations/android/xmpp/processor/MessageProcessor.java +++ b/app/src/main/java/im/conversations/android/xmpp/processor/MessageProcessor.java @@ -6,6 +6,7 @@ import im.conversations.android.transformer.TransformationFactory; import im.conversations.android.transformer.Transformer; import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.manager.ArchiveManager; +import im.conversations.android.xmpp.manager.AxolotlManager; import im.conversations.android.xmpp.manager.CarbonsManager; import im.conversations.android.xmpp.manager.ChatStateManager; import im.conversations.android.xmpp.manager.JingleConnectionManager; @@ -83,7 +84,9 @@ public class MessageProcessor extends XmppConnection.Delegate implements Consume final boolean sendReceipts; if (transformation.isAnythingToTransform()) { final var database = ConversationsDatabase.getInstance(context); - final var transformer = new Transformer(database, getAccount()); + final var axolotlService = + connection.getManager(AxolotlManager.class).getAxolotlService(); + final var transformer = new Transformer(getAccount(), database, axolotlService); sendReceipts = transformer.transform(transformation); } else { sendReceipts = true; @@ -96,8 +99,6 @@ public class MessageProcessor extends XmppConnection.Delegate implements Consume if (chatState != null) { getManager(ChatStateManager.class).handle(from, chatState); } - - // TODO pass JMI to JingleManager } private boolean isRoot() {