implement self healing omemo
after receiving a SignalMessage that can’t be decrypted because of broken sessions Conversations will attempt to grab a new pre key bundle and send a new PreKeySignalMessage wrapped in a key transport message.
This commit is contained in:
parent
f1e1c4a78d
commit
c02676ea4c
|
@ -82,9 +82,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
private final SerialSingleThreadExecutor executor;
|
private final SerialSingleThreadExecutor executor;
|
||||||
private int numPublishTriesOnEmptyPep = 0;
|
private int numPublishTriesOnEmptyPep = 0;
|
||||||
private boolean pepBroken = false;
|
private boolean pepBroken = false;
|
||||||
|
private final Set<SignalProtocolAddress> healingAttempts = new HashSet<>();
|
||||||
private int lastDeviceListNotificationHash = 0;
|
private int lastDeviceListNotificationHash = 0;
|
||||||
private final HashSet<Integer> cleanedOwnDeviceIds = new HashSet<>();
|
private final HashSet<Integer> cleanedOwnDeviceIds = new HashSet<>();
|
||||||
private Set<XmppAxolotlSession> postponedSessions = new HashSet<>(); //sessions stored here will receive after mam catchup treatment
|
private Set<XmppAxolotlSession> postponedSessions = new HashSet<>(); //sessions stored here will receive after mam catchup treatment
|
||||||
|
private Set<SignalProtocolAddress> postponedHealing = new HashSet<>(); //addresses stored here will need a healing notification after mam catchup
|
||||||
|
|
||||||
private AtomicBoolean changeAccessMode = new AtomicBoolean(false);
|
private AtomicBoolean changeAccessMode = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
@ -390,6 +392,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
this.pepBroken = false;
|
this.pepBroken = false;
|
||||||
this.numPublishTriesOnEmptyPep = 0;
|
this.numPublishTriesOnEmptyPep = 0;
|
||||||
this.lastDeviceListNotificationHash = 0;
|
this.lastDeviceListNotificationHash = 0;
|
||||||
|
this.healingAttempts.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearErrorsInFetchStatusMap(Jid jid) {
|
public void clearErrorsInFetchStatusMap(Jid jid) {
|
||||||
|
@ -1071,7 +1074,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OnSessionBuildFromPep {
|
||||||
|
void onSessionBuildSuccessful();
|
||||||
|
void onSessionBuildFailed();
|
||||||
|
}
|
||||||
|
|
||||||
private void buildSessionFromPEP(final SignalProtocolAddress address) {
|
private void buildSessionFromPEP(final SignalProtocolAddress address) {
|
||||||
|
buildSessionFromPEP(address, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildSessionFromPEP(final SignalProtocolAddress address, OnSessionBuildFromPep callback) {
|
||||||
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString());
|
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString());
|
||||||
if (address.equals(getOwnAxolotlAddress())) {
|
if (address.equals(getOwnAxolotlAddress())) {
|
||||||
throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
|
throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
|
||||||
|
@ -1092,6 +1104,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
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);
|
fetchStatusMap.put(address, FetchStatus.ERROR);
|
||||||
finishBuildingSessionsFromPEP(address);
|
finishBuildingSessionsFromPEP(address);
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onSessionBuildFailed();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
|
@ -1100,6 +1115,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
//should never happen
|
//should never happen
|
||||||
fetchStatusMap.put(address, FetchStatus.ERROR);
|
fetchStatusMap.put(address, FetchStatus.ERROR);
|
||||||
finishBuildingSessionsFromPEP(address);
|
finishBuildingSessionsFromPEP(address);
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onSessionBuildFailed();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1114,7 +1132,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
|
XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
|
||||||
sessions.put(address, session);
|
sessions.put(address, session);
|
||||||
if (Config.X509_VERIFICATION) {
|
if (Config.X509_VERIFICATION) {
|
||||||
verifySessionWithPEP(session);
|
verifySessionWithPEP(session); //TODO; maybe inject callback in here too
|
||||||
} else {
|
} else {
|
||||||
FingerprintStatus status = getFingerprintTrust(CryptoHelper.bytesToHex(bundle.getIdentityKey().getPublicKey().serialize()));
|
FingerprintStatus status = getFingerprintTrust(CryptoHelper.bytesToHex(bundle.getIdentityKey().getPublicKey().serialize()));
|
||||||
FetchStatus fetchStatus;
|
FetchStatus fetchStatus;
|
||||||
|
@ -1127,6 +1145,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
}
|
}
|
||||||
fetchStatusMap.put(address, fetchStatus);
|
fetchStatusMap.put(address, fetchStatus);
|
||||||
finishBuildingSessionsFromPEP(address);
|
finishBuildingSessionsFromPEP(address);
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onSessionBuildSuccessful();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (UntrustedIdentityException | InvalidKeyException e) {
|
} catch (UntrustedIdentityException | InvalidKeyException e) {
|
||||||
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
|
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
|
||||||
|
@ -1136,6 +1157,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
if (oneOfOurs && cleanedOwnDeviceIds.add(address.getDeviceId())) {
|
if (oneOfOurs && cleanedOwnDeviceIds.add(address.getDeviceId())) {
|
||||||
removeFromDeviceAnnouncement(address.getDeviceId());
|
removeFromDeviceAnnouncement(address.getDeviceId());
|
||||||
}
|
}
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onSessionBuildFailed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fetchStatusMap.put(address, FetchStatus.ERROR);
|
fetchStatusMap.put(address, FetchStatus.ERROR);
|
||||||
|
@ -1146,6 +1170,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
if (oneOfOurs && itemNotFound && cleanedOwnDeviceIds.add(address.getDeviceId())) {
|
if (oneOfOurs && itemNotFound && cleanedOwnDeviceIds.add(address.getDeviceId())) {
|
||||||
removeFromDeviceAnnouncement(address.getDeviceId());
|
removeFromDeviceAnnouncement(address.getDeviceId());
|
||||||
}
|
}
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onSessionBuildFailed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1391,11 +1418,15 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
}
|
}
|
||||||
|
|
||||||
private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
|
private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
|
||||||
SignalProtocolAddress senderAddress = new SignalProtocolAddress(message.getFrom().toString(),
|
SignalProtocolAddress senderAddress = new SignalProtocolAddress(message.getFrom().toString(), message.getSenderDeviceId());
|
||||||
message.getSenderDeviceId());
|
return getReceivingSession(senderAddress);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private XmppAxolotlSession getReceivingSession(SignalProtocolAddress senderAddress) {
|
||||||
XmppAxolotlSession session = sessions.get(senderAddress);
|
XmppAxolotlSession session = sessions.get(senderAddress);
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
|
//Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
|
||||||
session = recreateUncachedSession(senderAddress);
|
session = recreateUncachedSession(senderAddress);
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
|
session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
|
||||||
|
@ -1404,7 +1435,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) throws NotEncryptedForThisDeviceException {
|
public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) throws NotEncryptedForThisDeviceException, BrokenSessionException {
|
||||||
XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
|
XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
|
||||||
|
|
||||||
XmppAxolotlSession session = getReceivingSession(message);
|
XmppAxolotlSession session = getReceivingSession(message);
|
||||||
|
@ -1421,8 +1452,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
} catch (final BrokenSessionException e) {
|
||||||
|
throw e;
|
||||||
} catch (CryptoFailedException e) {
|
} catch (CryptoFailedException e) {
|
||||||
Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from " + message.getFrom() + ": " + e.getMessage());
|
Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from " + message.getFrom(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.isFresh() && plaintextMessage != null) {
|
if (session.isFresh() && plaintextMessage != null) {
|
||||||
|
@ -1432,6 +1465,35 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
return plaintextMessage;
|
return plaintextMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void reportBrokenSessionException(BrokenSessionException e, boolean postpone) {
|
||||||
|
Log.e(Config.LOGTAG,account.getJid().asBareJid()+": broken session with "+e.getSignalProtocolAddress().toString()+" detected", e);
|
||||||
|
if (postpone) {
|
||||||
|
postponedHealing.add(e.getSignalProtocolAddress());
|
||||||
|
} else {
|
||||||
|
notifyRequiresHealing(e.getSignalProtocolAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyRequiresHealing(final SignalProtocolAddress signalProtocolAddress) {
|
||||||
|
if (healingAttempts.add(signalProtocolAddress)) {
|
||||||
|
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": attempt to heal "+signalProtocolAddress);
|
||||||
|
buildSessionFromPEP(signalProtocolAddress, new OnSessionBuildFromPep() {
|
||||||
|
@Override
|
||||||
|
public void onSessionBuildSuccessful() {
|
||||||
|
Log.d(Config.LOGTAG, "successfully build new session from pep after detecting broken session");
|
||||||
|
completeSession(getReceivingSession(signalProtocolAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSessionBuildFailed() {
|
||||||
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to build new session from pep after detecting broken session");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": do not attempt to heal "+signalProtocolAddress+" again");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void postPreKeyMessageHandling(final XmppAxolotlSession session, int preKeyId, final boolean postpone) {
|
private void postPreKeyMessageHandling(final XmppAxolotlSession session, int preKeyId, final boolean postpone) {
|
||||||
if (postpone) {
|
if (postpone) {
|
||||||
postponedSessions.add(session);
|
postponedSessions.add(session);
|
||||||
|
@ -1451,6 +1513,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
completeSession(iterator.next());
|
completeSession(iterator.next());
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
|
Iterator<SignalProtocolAddress> postponedHealingAttemptsIterator = postponedHealing.iterator();
|
||||||
|
while (postponedHealingAttemptsIterator.hasNext()) {
|
||||||
|
notifyRequiresHealing(postponedHealingAttemptsIterator.next());
|
||||||
|
postponedHealingAttemptsIterator.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void completeSession(XmppAxolotlSession session) {
|
private void completeSession(XmppAxolotlSession session) {
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package eu.siacs.conversations.crypto.axolotl;
|
||||||
|
|
||||||
|
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||||
|
|
||||||
|
public class BrokenSessionException extends CryptoFailedException {
|
||||||
|
|
||||||
|
private final SignalProtocolAddress signalProtocolAddress;
|
||||||
|
|
||||||
|
public BrokenSessionException(SignalProtocolAddress address, Exception e) {
|
||||||
|
super(e);
|
||||||
|
this.signalProtocolAddress = address;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignalProtocolAddress getSignalProtocolAddress() {
|
||||||
|
return signalProtocolAddress;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,10 @@ public class CryptoFailedException extends Exception {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CryptoFailedException(String msg, Exception e) {
|
||||||
|
super(msg, e);
|
||||||
|
}
|
||||||
|
|
||||||
public CryptoFailedException(Exception e){
|
public CryptoFailedException(Exception e){
|
||||||
super(e);
|
super(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,34 +79,35 @@ public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public byte[] processReceiving(AxolotlKey encryptedKey) throws CryptoFailedException {
|
byte[] processReceiving(AxolotlKey encryptedKey) throws CryptoFailedException {
|
||||||
byte[] plaintext;
|
byte[] plaintext;
|
||||||
FingerprintStatus status = getTrust();
|
FingerprintStatus status = getTrust();
|
||||||
if (!status.isCompromised()) {
|
if (!status.isCompromised()) {
|
||||||
try {
|
try {
|
||||||
if (encryptedKey.prekey) {
|
if (encryptedKey.prekey) {
|
||||||
PreKeySignalMessage preKeySignalMessage = new PreKeySignalMessage(encryptedKey.key);
|
PreKeySignalMessage preKeySignalMessage = new PreKeySignalMessage(encryptedKey.key);
|
||||||
Optional<Integer> optionalPreKeyId = preKeySignalMessage.getPreKeyId();
|
Optional<Integer> optionalPreKeyId = preKeySignalMessage.getPreKeyId();
|
||||||
IdentityKey identityKey = preKeySignalMessage.getIdentityKey();
|
IdentityKey identityKey = preKeySignalMessage.getIdentityKey();
|
||||||
if (!optionalPreKeyId.isPresent()) {
|
if (!optionalPreKeyId.isPresent()) {
|
||||||
throw new CryptoFailedException("PreKeyWhisperMessage did not contain a PreKeyId");
|
throw new CryptoFailedException("PreKeyWhisperMessage did not contain a PreKeyId");
|
||||||
}
|
}
|
||||||
preKeyId = optionalPreKeyId.get();
|
preKeyId = optionalPreKeyId.get();
|
||||||
if (this.identityKey != null && !this.identityKey.equals(identityKey)) {
|
if (this.identityKey != null && !this.identityKey.equals(identityKey)) {
|
||||||
throw new CryptoFailedException("Received PreKeyWhisperMessage but preexisting identity key changed.");
|
throw new CryptoFailedException("Received PreKeyWhisperMessage but preexisting identity key changed.");
|
||||||
}
|
}
|
||||||
this.identityKey = identityKey;
|
this.identityKey = identityKey;
|
||||||
plaintext = cipher.decrypt(preKeySignalMessage);
|
plaintext = cipher.decrypt(preKeySignalMessage);
|
||||||
} else {
|
} else {
|
||||||
SignalMessage signalMessage = new SignalMessage(encryptedKey.key);
|
SignalMessage signalMessage = new SignalMessage(encryptedKey.key);
|
||||||
plaintext = cipher.decrypt(signalMessage);
|
try {
|
||||||
preKeyId = null; //better safe than sorry because we use that to do special after prekey handling
|
plaintext = cipher.decrypt(signalMessage);
|
||||||
}
|
} catch (InvalidMessageException | NoSessionException e) {
|
||||||
} catch (InvalidVersionException | InvalidKeyException | LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException | InvalidKeyIdException | UntrustedIdentityException e) {
|
throw new BrokenSessionException(this.remoteAddress, e);
|
||||||
if (!(e instanceof DuplicateMessageException)) {
|
}
|
||||||
e.printStackTrace();
|
preKeyId = null; //better safe than sorry because we use that to do special after prekey handling
|
||||||
}
|
}
|
||||||
throw new CryptoFailedException("Error decrypting WhisperMessage " + e.getClass().getSimpleName() + ": " + e.getMessage());
|
} catch (InvalidVersionException | InvalidKeyException | LegacyMessageException | InvalidMessageException | DuplicateMessageException | InvalidKeyIdException | UntrustedIdentityException e) {
|
||||||
|
throw new CryptoFailedException("Error decrypting SignalMessage", e);
|
||||||
}
|
}
|
||||||
if (!status.isActive()) {
|
if (!status.isActive()) {
|
||||||
setTrust(status.toActive());
|
setTrust(status.toActive());
|
||||||
|
|
|
@ -702,6 +702,20 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean possibleDuplicate(final String serverMsgId, final String remoteMsgId) {
|
||||||
|
if (serverMsgId == null || remoteMsgId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
synchronized (this.messages) {
|
||||||
|
for(Message message : this.messages) {
|
||||||
|
if (serverMsgId.equals(message.getServerMsgId()) || remoteMsgId.equals(message.getRemoteMsgId())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public MamReference getLastMessageTransmitted() {
|
public MamReference getLastMessageTransmitted() {
|
||||||
final MamReference lastClear = getLastClearHistory();
|
final MamReference lastClear = getLastClearHistory();
|
||||||
MamReference lastReceived = new MamReference(0);
|
MamReference lastReceived = new MamReference(0);
|
||||||
|
|
|
@ -47,6 +47,7 @@ public class Message extends AbstractEntity {
|
||||||
public static final int ENCRYPTION_DECRYPTION_FAILED = 4;
|
public static final int ENCRYPTION_DECRYPTION_FAILED = 4;
|
||||||
public static final int ENCRYPTION_AXOLOTL = 5;
|
public static final int ENCRYPTION_AXOLOTL = 5;
|
||||||
public static final int ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE = 6;
|
public static final int ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE = 6;
|
||||||
|
public static final int ENCRYPTION_AXOLOTL_FAILED = 7;
|
||||||
|
|
||||||
public static final int TYPE_TEXT = 0;
|
public static final int TYPE_TEXT = 0;
|
||||||
public static final int TYPE_IMAGE = 1;
|
public static final int TYPE_IMAGE = 1;
|
||||||
|
@ -883,7 +884,7 @@ public class Message extends AbstractEntity {
|
||||||
if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) {
|
if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) {
|
||||||
return ENCRYPTION_PGP;
|
return ENCRYPTION_PGP;
|
||||||
}
|
}
|
||||||
if (encryption == ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) {
|
if (encryption == ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || encryption == ENCRYPTION_AXOLOTL_FAILED) {
|
||||||
return ENCRYPTION_AXOLOTL;
|
return ENCRYPTION_AXOLOTL;
|
||||||
}
|
}
|
||||||
return encryption;
|
return encryption;
|
||||||
|
|
|
@ -16,6 +16,7 @@ import java.util.UUID;
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.BrokenSessionException;
|
||||||
import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException;
|
import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException;
|
||||||
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
|
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
@ -107,7 +108,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, boolean postpone) {
|
private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, boolean checkedForDuplicates, boolean postpone) {
|
||||||
final AxolotlService service = conversation.getAccount().getAxolotlService();
|
final AxolotlService service = conversation.getAccount().getAxolotlService();
|
||||||
final XmppAxolotlMessage xmppAxolotlMessage;
|
final XmppAxolotlMessage xmppAxolotlMessage;
|
||||||
try {
|
try {
|
||||||
|
@ -120,6 +121,14 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage;
|
final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage;
|
||||||
try {
|
try {
|
||||||
plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
|
plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
|
||||||
|
} catch (BrokenSessionException e) {
|
||||||
|
if (checkedForDuplicates) {
|
||||||
|
service.reportBrokenSessionException(e, postpone);
|
||||||
|
return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG,"ignoring broken session exception because checkForDuplicase failed");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
} catch (NotEncryptedForThisDeviceException e) {
|
} catch (NotEncryptedForThisDeviceException e) {
|
||||||
return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status);
|
return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status);
|
||||||
}
|
}
|
||||||
|
@ -424,12 +433,15 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
fallbacksBySourceId = Collections.emptySet();
|
fallbacksBySourceId = Collections.emptySet();
|
||||||
origin = from;
|
origin = from;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean checkedForDuplicates = serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId);
|
||||||
|
|
||||||
if (origin != null) {
|
if (origin != null) {
|
||||||
message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, query != null);
|
message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, checkedForDuplicates,query != null);
|
||||||
} else {
|
} else {
|
||||||
Message trial = null;
|
Message trial = null;
|
||||||
for (Jid fallback : fallbacksBySourceId) {
|
for (Jid fallback : fallbacksBySourceId) {
|
||||||
trial = parseAxolotlChat(axolotlEncrypted, fallback, conversation, status, query != null);
|
trial = parseAxolotlChat(axolotlEncrypted, fallback, conversation, status, checkedForDuplicates && fallbacksBySourceId.size() == 1, query != null);
|
||||||
if (trial != null) {
|
if (trial != null) {
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": decoded muc message using fallback");
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": decoded muc message using fallback");
|
||||||
origin = fallback;
|
origin = fallback;
|
||||||
|
@ -606,7 +618,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
|
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||||
notify = conversation.getAccount().getPgpDecryptionService().decrypt(message, notify);
|
notify = conversation.getAccount().getPgpDecryptionService().decrypt(message, notify);
|
||||||
} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) {
|
} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
|
||||||
notify = false;
|
notify = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -751,7 +751,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
|
|
||||||
public Cursor getMessageSearchCursor(List<String> term) {
|
public Cursor getMessageSearchCursor(List<String> term) {
|
||||||
SQLiteDatabase db = this.getReadableDatabase();
|
SQLiteDatabase db = this.getReadableDatabase();
|
||||||
String SQL = "SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + '.' + Conversation.CONTACTJID + ',' + Conversation.TABLENAME + '.' + Conversation.ACCOUNT + ',' + Conversation.TABLENAME + '.' + Conversation.MODE + " FROM " + Message.TABLENAME + " join " + Conversation.TABLENAME + " on " + Message.TABLENAME + '.' + Message.CONVERSATION + '=' + Conversation.TABLENAME + '.' + Conversation.UUID + " join messages_index ON messages_index.uuid=messages.uuid where " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + ',' + Message.ENCRYPTION_PGP + ',' + Message.ENCRYPTION_DECRYPTION_FAILED + ") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + ',' + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ? ORDER BY " + Message.TIME_SENT + " DESC limit " + Config.MAX_SEARCH_RESULTS;
|
String SQL = "SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + '.' + Conversation.CONTACTJID + ',' + Conversation.TABLENAME + '.' + Conversation.ACCOUNT + ',' + Conversation.TABLENAME + '.' + Conversation.MODE + " FROM " + Message.TABLENAME + " join " + Conversation.TABLENAME + " on " + Message.TABLENAME + '.' + Message.CONVERSATION + '=' + Conversation.TABLENAME + '.' + Conversation.UUID + " join messages_index ON messages_index.uuid=messages.uuid where " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + ',' + Message.ENCRYPTION_PGP + ',' + Message.ENCRYPTION_DECRYPTION_FAILED + ','+Message.ENCRYPTION_AXOLOTL_FAILED+") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + ',' + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ? ORDER BY " + Message.TIME_SENT + " DESC limit " + Config.MAX_SEARCH_RESULTS;
|
||||||
Log.d(Config.LOGTAG, "search term: " + FtsUtils.toMatchString(term));
|
Log.d(Config.LOGTAG, "search term: " + FtsUtils.toMatchString(term));
|
||||||
return db.rawQuery(SQL, new String[]{FtsUtils.toMatchString(term)});
|
return db.rawQuery(SQL, new String[]{FtsUtils.toMatchString(term)});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1096,7 +1096,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
||||||
}
|
}
|
||||||
if (m.getType() != Message.TYPE_STATUS) {
|
if (m.getType() != Message.TYPE_STATUS) {
|
||||||
|
|
||||||
if (m.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) {
|
if (m.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || m.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -775,6 +775,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
||||||
displayInfoMessage(viewHolder, activity.getString(R.string.decryption_failed), darkBackground);
|
displayInfoMessage(viewHolder, activity.getString(R.string.decryption_failed), darkBackground);
|
||||||
} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) {
|
} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) {
|
||||||
displayInfoMessage(viewHolder, activity.getString(R.string.not_encrypted_for_this_device), darkBackground);
|
displayInfoMessage(viewHolder, activity.getString(R.string.not_encrypted_for_this_device), darkBackground);
|
||||||
|
} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
|
||||||
|
displayInfoMessage(viewHolder, activity.getString(R.string.omemo_decryption_failed), darkBackground);
|
||||||
} else {
|
} else {
|
||||||
if (message.isGeoUri()) {
|
if (message.isGeoUri()) {
|
||||||
displayLocationMessage(viewHolder, message);
|
displayLocationMessage(viewHolder, message);
|
||||||
|
|
|
@ -264,6 +264,7 @@ public final class CryptoHelper {
|
||||||
return R.string.encryption_choice_otr;
|
return R.string.encryption_choice_otr;
|
||||||
case Message.ENCRYPTION_AXOLOTL:
|
case Message.ENCRYPTION_AXOLOTL:
|
||||||
case Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE:
|
case Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE:
|
||||||
|
case Message.ENCRYPTION_AXOLOTL_FAILED:
|
||||||
return R.string.encryption_choice_omemo;
|
return R.string.encryption_choice_omemo;
|
||||||
case Message.ENCRYPTION_NONE:
|
case Message.ENCRYPTION_NONE:
|
||||||
return R.string.encryption_choice_unencrypted;
|
return R.string.encryption_choice_unencrypted;
|
||||||
|
|
|
@ -292,6 +292,8 @@ public class UIHelper {
|
||||||
return new Pair<>(context.getString(R.string.decryption_failed), true);
|
return new Pair<>(context.getString(R.string.decryption_failed), true);
|
||||||
} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) {
|
} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) {
|
||||||
return new Pair<>(context.getString(R.string.not_encrypted_for_this_device), true);
|
return new Pair<>(context.getString(R.string.not_encrypted_for_this_device), true);
|
||||||
|
} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
|
||||||
|
return new Pair<>(context.getString(R.string.omemo_decryption_failed), true);
|
||||||
} else if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) {
|
} else if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) {
|
||||||
return new Pair<>(getFileDescriptionString(context, message), true);
|
return new Pair<>(getFileDescriptionString(context, message), true);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -697,6 +697,7 @@
|
||||||
<string name="medium">Medium</string>
|
<string name="medium">Medium</string>
|
||||||
<string name="large">Large</string>
|
<string name="large">Large</string>
|
||||||
<string name="not_encrypted_for_this_device">Message was not encrypted for this device.</string>
|
<string name="not_encrypted_for_this_device">Message was not encrypted for this device.</string>
|
||||||
|
<string name="omemo_decryption_failed">Failed to decrypt OMEMO message.</string>
|
||||||
<string name="undo">undo</string>
|
<string name="undo">undo</string>
|
||||||
<string name="location_disabled">Location sharing is disabled</string>
|
<string name="location_disabled">Location sharing is disabled</string>
|
||||||
<string name="action_fix_to_location">Fix position</string>
|
<string name="action_fix_to_location">Fix position</string>
|
||||||
|
|
Loading…
Reference in a new issue