decrypt omemo messages

This commit is contained in:
Daniel Gultsch 2023-02-24 17:36:49 +01:00
parent 49b4f54285
commit 2abcb1b4e4
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
19 changed files with 111 additions and 42 deletions

View file

@ -54,7 +54,7 @@ public class MessageTransformationTest {
final long id = database.accountDao().insert(account); final long id = database.accountDao().insert(account);
this.transformer = this.transformer =
new Transformer(database, database.accountDao().getEnabledAccount(id).get()); new Transformer(database.accountDao().getEnabledAccount(id).get(), database);
} }
@Test @Test

View file

@ -81,7 +81,10 @@
android:name=".service.ForegroundService" android:name=".service.ForegroundService"
android:exported="false"/> android:exported="false"/>
<service android:name=".service.RtpSessionService" android:exported="false" android:foregroundServiceType="phoneCall"/> <service
android:name=".service.RtpSessionService"
android:exported="false"
android:foregroundServiceType="phoneCall" />
<receiver <receiver
android:name=".receiver.EventReceiver" android:name=".receiver.EventReceiver"
@ -95,16 +98,15 @@
<activity <activity
android:name="im.conversations.android.ui.activity.MainActivity" android:name="im.conversations.android.ui.activity.MainActivity"
android:windowSoftInputMode="adjustResize" android:exported="true"
android:launchMode="singleTask" android:launchMode="singleTask"
android:exported="true"> android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity android:name="im.conversations.android.ui.activity.SettingsActivity" />
android:name="im.conversations.android.ui.activity.SettingsActivity" />
<activity <activity
android:name="im.conversations.android.ui.activity.SetupActivity" android:name="im.conversations.android.ui.activity.SetupActivity"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />

View file

@ -1,15 +1,15 @@
package im.conversations.android; package im.conversations.android;
import android.content.Context; import im.conversations.android.database.ConversationsDatabase;
import im.conversations.android.database.model.Account; import im.conversations.android.database.model.Account;
public abstract class AbstractAccountService { public abstract class AbstractAccountService {
protected Context context;
protected Account account; protected Account account;
protected ConversationsDatabase database;
protected AbstractAccountService(final Context context, final Account account) { protected AbstractAccountService(final Account account, final ConversationsDatabase database) {
this.context = context;
this.account = account; this.account = account;
this.database = database;
} }
} }

View file

@ -24,4 +24,8 @@ public class AxolotlPayload {
public String payloadAsString() { public String payloadAsString() {
return new String(payload, StandardCharsets.UTF_8); return new String(payload, StandardCharsets.UTF_8);
} }
public boolean hasPayload() {
return payload != null;
}
} }

View file

@ -1,11 +1,11 @@
package im.conversations.android.axolotl; package im.conversations.android.axolotl;
import android.content.Context;
import android.os.Build; import android.os.Build;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import eu.siacs.conversations.xmpp.jingle.OmemoVerification; import eu.siacs.conversations.xmpp.jingle.OmemoVerification;
import im.conversations.android.AbstractAccountService; import im.conversations.android.AbstractAccountService;
import im.conversations.android.database.AxolotlDatabaseStore; import im.conversations.android.database.AxolotlDatabaseStore;
import im.conversations.android.database.ConversationsDatabase;
import im.conversations.android.database.model.Account; import im.conversations.android.database.model.Account;
import im.conversations.android.xmpp.model.axolotl.Encrypted; import im.conversations.android.xmpp.model.axolotl.Encrypted;
import im.conversations.android.xmpp.model.axolotl.Header; 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.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import org.jxmpp.jid.Jid; import org.jxmpp.jid.Jid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.DuplicateMessageException; import org.whispersystems.libsignal.DuplicateMessageException;
import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.InvalidKeyException;
@ -38,6 +40,8 @@ import org.whispersystems.libsignal.state.SignalProtocolStore;
public class AxolotlService extends AbstractAccountService { 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 KEY_TYPE = "AES";
public static final String CIPHER_MODE = "AES/GCM/NoPadding"; public static final String CIPHER_MODE = "AES/GCM/NoPadding";
@ -45,9 +49,10 @@ public class AxolotlService extends AbstractAccountService {
private final SignalProtocolStore signalProtocolStore; private final SignalProtocolStore signalProtocolStore;
public AxolotlService(final Context context, final Account account) { public AxolotlService(
super(context, account); final Account account, final ConversationsDatabase conversationsDatabase) {
this.signalProtocolStore = new AxolotlDatabaseStore(context, account); super(account, conversationsDatabase);
this.signalProtocolStore = new AxolotlDatabaseStore(account, conversationsDatabase);
} }
private AxolotlSession buildReceivingSession( private AxolotlSession buildReceivingSession(
@ -119,6 +124,10 @@ public class AxolotlService extends AbstractAccountService {
final Header header = encrypted.getHeader(); final Header header = encrypted.getHeader();
final Key ourKey = header.getKey(signalProtocolStore.getLocalRegistrationId()); final Key ourKey = header.getKey(signalProtocolStore.getLocalRegistrationId());
if (ourKey == null) { if (ourKey == null) {
LOGGER.info(
"looking for {} in {}",
signalProtocolStore.getLocalRegistrationId(),
header.getKeys());
throw new NotEncryptedForThisDeviceException(); throw new NotEncryptedForThisDeviceException();
} }
final byte[] keyWithAuthTag; final byte[] keyWithAuthTag;

View file

@ -1,6 +1,5 @@
package im.conversations.android.database; package im.conversations.android.database;
import android.content.Context;
import im.conversations.android.AbstractAccountService; import im.conversations.android.AbstractAccountService;
import im.conversations.android.axolotl.AxolotlAddress; import im.conversations.android.axolotl.AxolotlAddress;
import im.conversations.android.database.dao.AxolotlDao; 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 class AxolotlDatabaseStore extends AbstractAccountService implements SignalProtocolStore {
public AxolotlDatabaseStore(final Context context, final Account account) { public AxolotlDatabaseStore(
super(context, account); final Account account, final ConversationsDatabase conversationsDatabase) {
super(account, conversationsDatabase);
} }
private AxolotlDao axolotlDao() { private AxolotlDao axolotlDao() {
return ConversationsDatabase.getInstance(context).axolotlDao(); return database.axolotlDao();
} }
@Override @Override

View file

@ -1,16 +1,19 @@
package im.conversations.android.tls; package im.conversations.android.tls;
import android.content.Context; import android.content.Context;
import im.conversations.android.AbstractAccountService;
import im.conversations.android.database.model.Account; import im.conversations.android.database.model.Account;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager; 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) { public TrustManager(final Context context, final Account account) {
super(context, account); this.context = context;
this.account = account;
} }
@Override @Override

View file

@ -4,6 +4,8 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; 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.ConversationsDatabase;
import im.conversations.android.database.model.Account; import im.conversations.android.database.model.Account;
import im.conversations.android.database.model.ChatIdentifier; import im.conversations.android.database.model.ChatIdentifier;
@ -36,10 +38,20 @@ public class Transformer {
private final ConversationsDatabase database; private final ConversationsDatabase database;
private final Account account; 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"); Preconditions.checkArgument(account != null, "Account must not be null");
this.database = database; this.database = database;
this.account = account; this.account = account;
this.axolotlService = axolotlService;
} }
public boolean transform(final MessageTransformation transformation) { public boolean transform(final MessageTransformation transformation) {
@ -72,11 +84,30 @@ public class Transformer {
chat, transformation.messageId, MessageState.error(transformation)); chat, transformation.messageId, MessageState.error(transformation));
return false; return false;
} }
final Replace messageCorrection = transformation.getExtension(Replace.class); final Replace messageCorrection = transformation.getExtension(Replace.class);
final Reactions reactions = transformation.getExtension(Reactions.class); final Reactions reactions = transformation.getExtension(Reactions.class);
final Retract retract = transformation.getExtension(Retract.class); final Retract retract = transformation.getExtension(Retract.class);
// TODO we need to remove fallbacks for reactions, retractions and potentially other things final Encrypted encrypted = transformation.getExtension(Encrypted.class);
final List<MessageContent> contents = parseContent(transformation); final List<MessageContent> 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 = final boolean identifiableSender =
Arrays.asList(Message.Type.NORMAL, Message.Type.CHAT).contains(messageType) Arrays.asList(Message.Type.NORMAL, Message.Type.CHAT).contains(messageType)

View file

@ -17,7 +17,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
import im.conversations.android.AbstractAccountService;
import im.conversations.android.BuildConfig; import im.conversations.android.BuildConfig;
import im.conversations.android.Conversations; import im.conversations.android.Conversations;
import im.conversations.android.IDs; import im.conversations.android.IDs;
@ -106,13 +105,16 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParserException; 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 Logger LOGGER = LoggerFactory.getLogger(XmppConnection.class);
private static final boolean EXTENDED_SM_LOGGING = false; private static final boolean EXTENDED_SM_LOGGING = false;
private static final int CONNECT_DISCO_TIMEOUT = 20; private static final int CONNECT_DISCO_TIMEOUT = 20;
private final Context context;
private final Account account;
private final SparseArray<Stanza> mStanzaQueue = new SparseArray<>(); private final SparseArray<Stanza> mStanzaQueue = new SparseArray<>();
private final Hashtable<String, Pair<Iq, Consumer<Iq>>> packetCallbacks = new Hashtable<>(); private final Hashtable<String, Pair<Iq, Consumer<Iq>>> packetCallbacks = new Hashtable<>();
private Socket socket; private Socket socket;
@ -155,7 +157,8 @@ public class XmppConnection extends AbstractAccountService implements Runnable {
private CountDownLatch mStreamCountDownLatch; private CountDownLatch mStreamCountDownLatch;
public XmppConnection(final Context context, final Account account) { public XmppConnection(final Context context, final Account account) {
super(context, account); this.context = context;
this.account = account;
this.connectionAddress = account.address; this.connectionAddress = account.address;
// these consumers are pure listeners; they dont have public method except for accept|apply // these consumers are pure listeners; they dont have public method except for accept|apply

View file

@ -22,6 +22,7 @@ import im.conversations.android.axolotl.AxolotlPayload;
import im.conversations.android.axolotl.AxolotlService; import im.conversations.android.axolotl.AxolotlService;
import im.conversations.android.axolotl.AxolotlSession; import im.conversations.android.axolotl.AxolotlSession;
import im.conversations.android.axolotl.EncryptionBuilder; import im.conversations.android.axolotl.EncryptionBuilder;
import im.conversations.android.database.ConversationsDatabase;
import im.conversations.android.xml.Element; import im.conversations.android.xml.Element;
import im.conversations.android.xml.Namespace; import im.conversations.android.xml.Namespace;
import im.conversations.android.xmpp.IqErrorException; import im.conversations.android.xmpp.IqErrorException;
@ -60,7 +61,13 @@ public class AxolotlManager extends AbstractManager {
public AxolotlManager(Context context, XmppConnection connection) { public AxolotlManager(Context context, XmppConnection connection) {
super(context, 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) { 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"); throw new IllegalStateException("No signed PreKeys have been created yet");
} }
bundle.setSignedPreKey( bundle.setSignedPreKey(
signedPreKeyRecord.getKeyPair().getPublicKey(), signedPreKeyRecord.getSignature()); signedPreKeyRecord.getId(),
bundle.setPreKeys(getDatabase().axolotlDao().getPreKeys(getAccount().id)); signedPreKeyRecord.getKeyPair().getPublicKey(),
signedPreKeyRecord.getSignature());
bundle.addPreKeys(getDatabase().axolotlDao().getPreKeys(getAccount().id));
LOGGER.info("bundle {}", bundle);
return bundle; return bundle;
} }

View file

@ -40,14 +40,16 @@ public class Bundle extends Extension {
identityKey.setContent(ecPublicKey); 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()); final var signedPreKey = this.addExtension(new SignedPreKey());
signedPreKey.setId(id);
signedPreKey.setContent(ecPublicKey); signedPreKey.setContent(ecPublicKey);
final var signedPreKeySignature = this.addExtension(new SignedPreKeySignature()); final var signedPreKeySignature = this.addExtension(new SignedPreKeySignature());
signedPreKeySignature.setContent(signature); signedPreKeySignature.setContent(signature);
} }
public void setPreKeys(final List<PreKeyRecord> preKeyRecords) { public void addPreKeys(final List<PreKeyRecord> preKeyRecords) {
final var preKeys = this.addExtension(new PreKeys()); final var preKeys = this.addExtension(new PreKeys());
for (final PreKeyRecord preKeyRecord : preKeyRecords) { for (final PreKeyRecord preKeyRecord : preKeyRecords) {
final var preKey = preKeys.addExtension(new PreKey()); final var preKey = preKeys.addExtension(new PreKey());

View file

@ -4,7 +4,7 @@ import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.ByteContent; import im.conversations.android.xmpp.model.ByteContent;
import im.conversations.android.xmpp.model.Extension; import im.conversations.android.xmpp.model.Extension;
@XmlElement @XmlElement(name = "iv")
public class IV extends Extension implements ByteContent { public class IV extends Extension implements ByteContent {
public IV() { public IV() {

View file

@ -16,6 +16,6 @@ public class PreKey extends Extension implements ECPublicKeyContent {
} }
public void setId(int id) { public void setId(int id) {
this.setAttribute("id", id); this.setAttribute("preKeyId", id);
} }
} }

View file

@ -14,4 +14,8 @@ public class SignedPreKey extends Extension implements ECPublicKeyContent {
public int getId() { public int getId() {
return Ints.saturatedCast(this.getLongAttribute("signedPreKeyId")); return Ints.saturatedCast(this.getLongAttribute("signedPreKeyId"));
} }
public void setId(final int id) {
this.setAttribute("signedPreKeyId", id);
}
} }

View file

@ -5,7 +5,7 @@ import im.conversations.android.annotation.XmlElement;
@XmlElement @XmlElement
public class Active extends ChatStateNotification { public class Active extends ChatStateNotification {
protected Active() { public Active() {
super(Active.class); super(Active.class);
} }
} }

View file

@ -5,7 +5,7 @@ import im.conversations.android.annotation.XmlElement;
@XmlElement @XmlElement
public class Composing extends ChatStateNotification { public class Composing extends ChatStateNotification {
protected Composing() { public Composing() {
super(Composing.class); super(Composing.class);
} }
} }

View file

@ -5,7 +5,7 @@ import im.conversations.android.annotation.XmlElement;
@XmlElement @XmlElement
public class Gone extends ChatStateNotification { public class Gone extends ChatStateNotification {
protected Gone() { public Gone() {
super(Gone.class); super(Gone.class);
} }
} }

View file

@ -5,7 +5,7 @@ import im.conversations.android.annotation.XmlElement;
@XmlElement @XmlElement
public class Inactive extends ChatStateNotification { public class Inactive extends ChatStateNotification {
protected Inactive() { public Inactive() {
super(Inactive.class); super(Inactive.class);
} }
} }

View file

@ -6,6 +6,7 @@ import im.conversations.android.transformer.TransformationFactory;
import im.conversations.android.transformer.Transformer; import im.conversations.android.transformer.Transformer;
import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.manager.ArchiveManager; 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.CarbonsManager;
import im.conversations.android.xmpp.manager.ChatStateManager; import im.conversations.android.xmpp.manager.ChatStateManager;
import im.conversations.android.xmpp.manager.JingleConnectionManager; import im.conversations.android.xmpp.manager.JingleConnectionManager;
@ -83,7 +84,9 @@ public class MessageProcessor extends XmppConnection.Delegate implements Consume
final boolean sendReceipts; final boolean sendReceipts;
if (transformation.isAnythingToTransform()) { if (transformation.isAnythingToTransform()) {
final var database = ConversationsDatabase.getInstance(context); 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); sendReceipts = transformer.transform(transformation);
} else { } else {
sendReceipts = true; sendReceipts = true;
@ -96,8 +99,6 @@ public class MessageProcessor extends XmppConnection.Delegate implements Consume
if (chatState != null) { if (chatState != null) {
getManager(ChatStateManager.class).handle(from, chatState); getManager(ChatStateManager.class).handle(from, chatState);
} }
// TODO pass JMI to JingleManager
} }
private boolean isRoot() { private boolean isRoot() {