Repair broken session with preKeyMessage

This commit is contained in:
Paul Schaub 2018-01-06 00:49:04 +01:00
parent a90e37ba53
commit 8b8c6e190a
5 changed files with 158 additions and 50 deletions

View File

@ -107,7 +107,7 @@ public class SignalOmemoRatchet
throw new CryptoFailedException(e);
}
catch (InvalidKeyIdException e) {
throw new NoRawSessionException(e);
throw new NoRawSessionException(sender, e);
}
catch (DuplicateMessageException e) {
LOGGER.log(Level.INFO, "Decryption of PreKeyMessage from " + sender +
@ -125,7 +125,7 @@ public class SignalOmemoRatchet
throw new AssertionError("Signals trust management MUST be disabled.");
}
catch (InvalidMessageException | NoSessionException e) {
throw new NoRawSessionException(e);
throw new NoRawSessionException(sender, e);
}
catch (LegacyMessageException e) {
throw new CryptoFailedException(e);

View File

@ -52,17 +52,9 @@ public final class OmemoConfiguration {
*/
private static boolean ADD_OMEMO_HINT_BODY = true;
/**
* Add Explicit Message Encryption hint (XEP-0380) to the message.
* TODO: REMOVE
*/
private static boolean ADD_EME_ENCRYPTION_HINT = true;
private static boolean REPAIR_BROKEN_SESSIONS_WITH_PREKEY_MESSAGES = true;
/**
* Add MAM storage hint to allow the server to store messages that do not contain a body.
* TODO: REMOVE
*/
private static boolean ADD_MAM_STORAGE_HINT = true;
private static boolean COMPLETE_SESSION_WITH_EMPTY_MESSAGE = true;
private static File FILE_BASED_OMEMO_STORE_DEFAULT_PATH = null;
@ -104,14 +96,31 @@ public final class OmemoConfiguration {
return DELETE_STALE_DEVICE_AFTER_HOURS;
}
/**
* Decide, whether signed preKeys are automatically rotated or not.
* It is highly recommended to rotate signed preKeys to preserve forward secrecy.
*
* @param renew automatically rotate signed preKeys?
*/
public static void setRenewOldSignedPreKeys(boolean renew) {
RENEW_OLD_SIGNED_PREKEYS = renew;
}
/**
* Determine, whether signed preKeys are automatically rotated or not.
*
* @return auto-rotate signed preKeys?
*/
public static boolean getRenewOldSignedPreKeys() {
return RENEW_OLD_SIGNED_PREKEYS;
}
/**
* Set the interval in hours, after which the published signed preKey should be renewed.
* This value should be between one or two weeks.
*
* @param hours hours after which signed preKeys should be rotated.
*/
public static void setRenewOldSignedPreKeysAfterHours(int hours) {
if (hours <= 0) {
throw new IllegalArgumentException("Hours must be greater than 0.");
@ -119,10 +128,23 @@ public final class OmemoConfiguration {
RENEW_OLD_SIGNED_PREKEYS_AFTER_HOURS = hours;
}
/**
* Get the interval in hours, after which the published signed preKey should be renewed.
* This value should be between one or two weeks.
*
* @return hours after which signed preKeys should be rotated.
*/
public static int getRenewOldSignedPreKeysAfterHours() {
return RENEW_OLD_SIGNED_PREKEYS_AFTER_HOURS;
}
/**
* Set the maximum number of signed preKeys that are cached until the oldest one gets deleted.
* This number should not be too small in order to prevent message loss, but also not too big
* to preserve forward secrecy.
*
* @param number number of cached signed preKeys.
*/
public static void setMaxNumberOfStoredSignedPreKeys(int number) {
if (number <= 0) {
throw new IllegalArgumentException("Number must be greater than 0.");
@ -130,34 +152,33 @@ public final class OmemoConfiguration {
MAX_NUMBER_OF_STORED_SIGNED_PREKEYS = number;
}
/**
* Return the maximum number of signed preKeys that are cached until the oldest one gets deleted.
* @return max number of cached signed preKeys.
*/
public static int getMaxNumberOfStoredSignedPreKeys() {
return MAX_NUMBER_OF_STORED_SIGNED_PREKEYS;
}
/**
* Decide, whether an OMEMO message should carry a plaintext hint about OMEMO encryption.
* Eg. "I sent you an OMEMO encrypted message..."
*
* @param addHint shall we add a hint?
*/
public static void setAddOmemoHintBody(boolean addHint) {
ADD_OMEMO_HINT_BODY = addHint;
}
/**
* Determine, whether an OMEMO message should carry a plaintext hint about OMEMO encryption.
*
* @return true, if a hint is added to the message.
*/
public static boolean getAddOmemoHintBody() {
return ADD_OMEMO_HINT_BODY;
}
public static void setAddEmeEncryptionHint(boolean addHint) {
ADD_EME_ENCRYPTION_HINT = addHint;
}
public static boolean getAddEmeEncryptionHint() {
return ADD_EME_ENCRYPTION_HINT;
}
public static void setAddMAMStorageProcessingHint(boolean addStorageHint) {
ADD_MAM_STORAGE_HINT = addStorageHint;
}
public static boolean getAddMAMStorageProcessingHint() {
return ADD_MAM_STORAGE_HINT;
}
public static void setFileBasedOmemoStoreDefaultPath(File path) {
FILE_BASED_OMEMO_STORE_DEFAULT_PATH = path;
}
@ -165,4 +186,44 @@ public final class OmemoConfiguration {
public static File getFileBasedOmemoStoreDefaultPath() {
return FILE_BASED_OMEMO_STORE_DEFAULT_PATH;
}
/**
* Determine, whether incoming messages, which have broken sessions should automatically be answered by an empty
* preKeyMessage in order to establish a new session.
*
* @return true if session should be repaired automatically.
*/
public static boolean getRepairBrokenSessionsWithPreKeyMessages() {
return REPAIR_BROKEN_SESSIONS_WITH_PREKEY_MESSAGES;
}
/**
* Decide, whether incoming messages, which have broken sessions should automatically be answered by an empty
* preKeyMessage in order to establish a new session.
*
* @param repair repair sessions?
*/
public static void setRepairBrokenSessionsWithPrekeyMessages(boolean repair) {
REPAIR_BROKEN_SESSIONS_WITH_PREKEY_MESSAGES = repair;
}
/**
* Determine, whether incoming preKeyMessages should automatically be answered by an empty message in order to
* complete the session.
*
* @return true if sessions should be completed.
*/
public static boolean getCompleteSessionWithEmptyMessage() {
return COMPLETE_SESSION_WITH_EMPTY_MESSAGE;
}
/**
* Decide, whether incoming preKeyMessages should automatically be answered by an empty message in order to
* complete the session.
*
* @param complete complete the session or not
*/
public static void setCompleteSessionWithEmptyMessage(boolean complete) {
COMPLETE_SESSION_WITH_EMPTY_MESSAGE = complete;
}
}

View File

@ -62,7 +62,7 @@ public abstract class OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @return decrypted message key.
*
* @throws CorruptedOmemoKeyException
* @throws NoRawSessionException
* @throws NoRawSessionException when no double ratchet session was found.
* @throws CryptoFailedException
* @throws UntrustedOmemoIdentityException
*/

View File

@ -1087,20 +1087,23 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
}
OmemoMessage.Received decrypted;
BareJid sender;
MultiUserChat muc = getMuc(manager.getConnection(), stanza.getFrom());
if (muc != null) {
Occupant occupant = muc.getOccupant(stanza.getFrom().asEntityFullJidIfPossible());
Jid occupantJid = occupant.getJid();
try {
MultiUserChat muc = getMuc(manager.getConnection(), stanza.getFrom());
if (muc != null) {
Occupant occupant = muc.getOccupant(stanza.getFrom().asEntityFullJidIfPossible());
Jid occupantJid = occupant.getJid();
if (occupantJid == null) {
LOGGER.log(Level.WARNING, "MUC message received, but there is no way to retrieve the senders Jid. " +
stanza.getFrom());
return;
}
if (occupantJid == null) {
LOGGER.log(Level.WARNING, "MUC message received, but there is no way to retrieve the senders Jid. " +
stanza.getFrom());
return;
}
BareJid sender = occupantJid.asBareJid();
try {
sender = occupantJid.asBareJid();
// try is for this
decrypted = decryptMessage(managerGuard, sender, element, OmemoMessage.CARBON.NONE);
if (element.isMessageElement()) {
@ -1108,14 +1111,10 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
} else {
manager.notifyOmemoMucKeyTransportMessageReceived(muc, stanza, decrypted);
}
} else {
sender = stanza.getFrom().asBareJid();
} catch (CorruptedOmemoKeyException | CryptoFailedException | NoRawSessionException e) {
LOGGER.log(Level.WARNING, "Could not decrypt incoming message: ", e);
}
} else {
BareJid sender = stanza.getFrom().asBareJid();
try {
// and this
decrypted = decryptMessage(managerGuard, sender, element, OmemoMessage.CARBON.NONE);
if (element.isMessageElement()) {
@ -1123,10 +1122,49 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
} else {
manager.notifyOmemoKeyTransportMessageReceived(stanza, decrypted);
}
} catch (CorruptedOmemoKeyException | CryptoFailedException | NoRawSessionException e) {
LOGGER.log(Level.WARNING, "Could not decrypt incoming message: ", e);
}
}
catch (NoRawSessionException e) {
OmemoDevice device = e.getDeviceWithoutSession();
LOGGER.log(Level.WARNING, "No raw session found for contact " + device + ". ", e);
if (OmemoConfiguration.getRepairBrokenSessionsWithPreKeyMessages()) {
repairBrokenSessionWithPreKeyMessage(managerGuard, device);
}
}
catch (CorruptedOmemoKeyException | CryptoFailedException e) {
LOGGER.log(Level.WARNING, "Could not decrypt incoming message: ", e);
}
}
/**
* Fetch and process a fresh bundle and send an empty preKeyMessage in order to establish a fresh session.
*
* @param managerGuard authenticated OmemoManager.
* @param brokenDevice device which session broke.
*/
private void repairBrokenSessionWithPreKeyMessage(OmemoManager.LoggedInOmemoManager managerGuard,
OmemoDevice brokenDevice)
{
LOGGER.log(Level.WARNING, "Attempt to repair the session by sending a fresh preKey message to "
+ brokenDevice);
OmemoManager manager = managerGuard.get();
try {
// Create fresh session and send new preKeyMessage.
buildFreshSessionWithDevice(manager.getConnection(), manager.getOwnDevice(), brokenDevice);
OmemoElement ratchetUpdate = createRatchetUpdateElement(managerGuard, brokenDevice);
Message m = new Message();
m.setTo(brokenDevice.getJid());
m.addExtension(ratchetUpdate);
manager.getConnection().sendStanza(m);
} catch (CannotEstablishOmemoSessionException | CorruptedOmemoKeyException e) {
LOGGER.log(Level.WARNING, "Unable to repair session with " + brokenDevice, e);
} catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException e) {
LOGGER.log(Level.WARNING, "Could not fetch fresh bundle for " + brokenDevice, e);
} catch (CryptoFailedException | NoSuchAlgorithmException e) {
LOGGER.log(Level.WARNING, "Could not create PreKeyMessage", e);
}
}
/**

View File

@ -16,6 +16,8 @@
*/
package org.jivesoftware.smackx.omemo.exceptions;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
/**
* Exception that gets thrown whenever a OmemoMessage arrives that no OmemoSession was found for to decrypt it.
*
@ -25,7 +27,14 @@ public class NoRawSessionException extends Exception {
private static final long serialVersionUID = 3466888654338119954L;
public NoRawSessionException(Exception e) {
private final OmemoDevice device;
public NoRawSessionException(OmemoDevice device, Exception e) {
super(e);
this.device = device;
}
public OmemoDevice getDeviceWithoutSession() {
return device;
}
}