create stub message contents for encryption failures

This commit is contained in:
Daniel Gultsch 2023-03-11 15:56:17 +01:00
parent ee1c938f2a
commit 7c820f7b32
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
7 changed files with 71 additions and 27 deletions

View file

@ -3,6 +3,7 @@ package im.conversations.android.axolotl;
import android.content.Context; 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 com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -12,6 +13,7 @@ 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.ConversationsDatabase;
import im.conversations.android.database.model.Account; import im.conversations.android.database.model.Account;
import im.conversations.android.transformer.MessageContentWrapper;
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;
import im.conversations.android.xmpp.model.axolotl.Key; import im.conversations.android.xmpp.model.axolotl.Key;
@ -109,6 +111,27 @@ public class AxolotlService extends AbstractAccountService {
return session; return session;
} }
public boolean decryptEmptyMessage(final BareJid from, final Encrypted encrypted) {
Preconditions.checkArgument(
!encrypted.hasPayload(), "Use decryptToMessageContent to decrypt payload messages");
try {
final var payload = decrypt(from, encrypted);
return !payload.hasPayload();
} catch (final AxolotlDecryptionException e) {
return false;
}
}
public MessageContentWrapper decryptToMessageContent(
final BareJid from, final Encrypted encrypted) {
Preconditions.checkArgument(encrypted.hasPayload());
try {
return MessageContentWrapper.ofAxolotl(decrypt(from, encrypted));
} catch (final AxolotlDecryptionException e) {
return MessageContentWrapper.ofAxolotlException(e);
}
}
public AxolotlPayload decrypt(final Jid from, final Encrypted encrypted) public AxolotlPayload decrypt(final Jid from, final Encrypted encrypted)
throws AxolotlDecryptionException { throws AxolotlDecryptionException {
final AxolotlPayload axolotlPayload; final AxolotlPayload axolotlPayload;
@ -150,10 +173,6 @@ 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

@ -2,6 +2,7 @@ package im.conversations.android.database.model;
public enum Encryption { public enum Encryption {
OMEMO, OMEMO,
FAILURE,
CLEARTEXT, CLEARTEXT,
PGP PGP
} }

View file

@ -7,5 +7,7 @@ public enum PartType {
MODERATION, MODERATION,
VIDEO_CALL, VIDEO_CALL,
AUDIO_CALL, AUDIO_CALL,
MISSED_CALL MISSED_CALL,
NOT_ENCRYPTED_FOR_THIS_DEVICE,
DECRYPTION_FAILURE
} }

View file

@ -2,9 +2,12 @@ package im.conversations.android.transformer;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.base.Throwables;
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.AxolotlPayload; import im.conversations.android.axolotl.AxolotlPayload;
import im.conversations.android.axolotl.NotEncryptedForThisDeviceException;
import im.conversations.android.database.model.Encryption; import im.conversations.android.database.model.Encryption;
import im.conversations.android.database.model.MessageContent; import im.conversations.android.database.model.MessageContent;
import im.conversations.android.database.model.PartType; import im.conversations.android.database.model.PartType;
@ -23,6 +26,10 @@ public class MessageContentWrapper {
Encryption.CLEARTEXT, Encryption.CLEARTEXT,
null); null);
private static final List<MessageContent> NOT_ENCRYPTED_FOR_THIS_DEVICE =
ImmutableList.of(
new MessageContent(null, PartType.NOT_ENCRYPTED_FOR_THIS_DEVICE, null, null));
public final List<MessageContent> contents; public final List<MessageContent> contents;
public final Encryption encryption; public final Encryption encryption;
public final IdentityKey identityKey; public final IdentityKey identityKey;
@ -86,6 +93,29 @@ public class MessageContentWrapper {
String.format("%s does not have payload", payload.getClass().getSimpleName())); String.format("%s does not have payload", payload.getClass().getSimpleName()));
} }
public static MessageContentWrapper ofAxolotlException(final AxolotlDecryptionException e) {
final Throwable cause = Throwables.getRootCause(e);
if (cause instanceof NotEncryptedForThisDeviceException) {
return new MessageContentWrapper(
NOT_ENCRYPTED_FOR_THIS_DEVICE, Encryption.FAILURE, null);
} else {
return new MessageContentWrapper(
ImmutableList.of(
new MessageContent(
null,
PartType.DECRYPTION_FAILURE,
exceptionToMessage(cause),
null)),
Encryption.FAILURE,
null);
}
}
private static String exceptionToMessage(final Throwable throwable) {
final String message = throwable.getMessage();
return message == null ? throwable.getClass().getSimpleName() : message;
}
public boolean isEmpty() { public boolean isEmpty() {
return this.contents.isEmpty(); return this.contents.isEmpty();
} }

View file

@ -4,7 +4,6 @@ import android.content.Context;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import im.conversations.android.axolotl.AxolotlDecryptionException;
import im.conversations.android.axolotl.AxolotlService; 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;
@ -131,18 +130,13 @@ public class Transformer {
final Encrypted encrypted = transformation.getExtension(Encrypted.class); final Encrypted encrypted = transformation.getExtension(Encrypted.class);
final MessageContentWrapper contents; final MessageContentWrapper contents;
if (encrypted != null) { if (encrypted != null) {
try { if (encrypted.hasPayload()) {
final var payload = contents =
axolotlService.decrypt(transformation.senderIdentity(), encrypted); axolotlService.decryptToMessageContent(
if (payload.hasPayload()) { transformation.senderIdentity(), encrypted);
contents = MessageContentWrapper.ofAxolotl(payload); } else {
} else { return axolotlService.decryptEmptyMessage(
return true; transformation.senderIdentity(), encrypted);
}
} catch (final AxolotlDecryptionException e) {
LOGGER.error("Could not decrypt message", e);
// TODO if message had payload create error message entry
return false;
} }
} else { } else {
// TODO we need to remove fallbacks for reactions, retractions and potentially other // TODO we need to remove fallbacks for reactions, retractions and potentially other

View file

@ -97,8 +97,9 @@ public class ArchiveManager extends AbstractManager {
final var transformation = final var transformation =
this.transformationFactory.create( this.transformationFactory.create(
forwardedMessage, stanzaId, receivedAt, privilegedExtensionBuilder.build()); forwardedMessage, stanzaId, receivedAt, privilegedExtensionBuilder.build());
// TODO only when there is something to transform if (transformation.isAnythingToTransform()) {
runningQuery.addTransformation(transformation); runningQuery.addTransformation(transformation);
}
} }
private ListenableFuture<Metadata> fetchMetadata(final Jid archive) { private ListenableFuture<Metadata> fetchMetadata(final Jid archive) {

View file

@ -20,6 +20,7 @@ import im.conversations.android.xmpp.model.mam.Result;
import im.conversations.android.xmpp.model.pubsub.event.Event; import im.conversations.android.xmpp.model.pubsub.event.Event;
import im.conversations.android.xmpp.model.stanza.Message; import im.conversations.android.xmpp.model.stanza.Message;
import im.conversations.android.xmpp.model.state.ChatStateNotification; import im.conversations.android.xmpp.model.state.ChatStateNotification;
import java.util.Arrays;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -71,12 +72,6 @@ public class MessageProcessor extends XmppConnection.Delegate implements Consume
return; return;
} }
LOGGER.debug(
"Message from {} with {} in level {}",
message.getFrom(),
message.getExtensionIds(),
this.level);
final var from = message.getFrom(); final var from = message.getFrom();
final var id = message.getId(); final var id = message.getId();
@ -92,7 +87,9 @@ public class MessageProcessor extends XmppConnection.Delegate implements Consume
} else { } else {
sendReceipts = true; sendReceipts = true;
} }
if (sendReceipts) { if (sendReceipts
&& Arrays.asList(Message.Type.CHAT, Message.Type.NORMAL)
.contains(message.getType())) {
getManager(ReceiptManager.class) getManager(ReceiptManager.class)
.received(from, id, transformation.deliveryReceiptRequests); .received(from, id, transformation.deliveryReceiptRequests);
} }