mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-23 20:42:06 +01:00
Repair broken session with preKeyMessage
This commit is contained in:
parent
a90e37ba53
commit
8b8c6e190a
5 changed files with 158 additions and 50 deletions
|
@ -107,7 +107,7 @@ public class SignalOmemoRatchet
|
||||||
throw new CryptoFailedException(e);
|
throw new CryptoFailedException(e);
|
||||||
}
|
}
|
||||||
catch (InvalidKeyIdException e) {
|
catch (InvalidKeyIdException e) {
|
||||||
throw new NoRawSessionException(e);
|
throw new NoRawSessionException(sender, e);
|
||||||
}
|
}
|
||||||
catch (DuplicateMessageException e) {
|
catch (DuplicateMessageException e) {
|
||||||
LOGGER.log(Level.INFO, "Decryption of PreKeyMessage from " + sender +
|
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.");
|
throw new AssertionError("Signals trust management MUST be disabled.");
|
||||||
}
|
}
|
||||||
catch (InvalidMessageException | NoSessionException e) {
|
catch (InvalidMessageException | NoSessionException e) {
|
||||||
throw new NoRawSessionException(e);
|
throw new NoRawSessionException(sender, e);
|
||||||
}
|
}
|
||||||
catch (LegacyMessageException e) {
|
catch (LegacyMessageException e) {
|
||||||
throw new CryptoFailedException(e);
|
throw new CryptoFailedException(e);
|
||||||
|
|
|
@ -52,17 +52,9 @@ public final class OmemoConfiguration {
|
||||||
*/
|
*/
|
||||||
private static boolean ADD_OMEMO_HINT_BODY = true;
|
private static boolean ADD_OMEMO_HINT_BODY = true;
|
||||||
|
|
||||||
/**
|
private static boolean REPAIR_BROKEN_SESSIONS_WITH_PREKEY_MESSAGES = true;
|
||||||
* Add Explicit Message Encryption hint (XEP-0380) to the message.
|
|
||||||
* TODO: REMOVE
|
|
||||||
*/
|
|
||||||
private static boolean ADD_EME_ENCRYPTION_HINT = true;
|
|
||||||
|
|
||||||
/**
|
private static boolean COMPLETE_SESSION_WITH_EMPTY_MESSAGE = 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 File FILE_BASED_OMEMO_STORE_DEFAULT_PATH = null;
|
private static File FILE_BASED_OMEMO_STORE_DEFAULT_PATH = null;
|
||||||
|
|
||||||
|
@ -104,14 +96,31 @@ public final class OmemoConfiguration {
|
||||||
return DELETE_STALE_DEVICE_AFTER_HOURS;
|
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) {
|
public static void setRenewOldSignedPreKeys(boolean renew) {
|
||||||
RENEW_OLD_SIGNED_PREKEYS = renew;
|
RENEW_OLD_SIGNED_PREKEYS = renew;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine, whether signed preKeys are automatically rotated or not.
|
||||||
|
*
|
||||||
|
* @return auto-rotate signed preKeys?
|
||||||
|
*/
|
||||||
public static boolean getRenewOldSignedPreKeys() {
|
public static boolean getRenewOldSignedPreKeys() {
|
||||||
return RENEW_OLD_SIGNED_PREKEYS;
|
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) {
|
public static void setRenewOldSignedPreKeysAfterHours(int hours) {
|
||||||
if (hours <= 0) {
|
if (hours <= 0) {
|
||||||
throw new IllegalArgumentException("Hours must be greater than 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;
|
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() {
|
public static int getRenewOldSignedPreKeysAfterHours() {
|
||||||
return RENEW_OLD_SIGNED_PREKEYS_AFTER_HOURS;
|
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) {
|
public static void setMaxNumberOfStoredSignedPreKeys(int number) {
|
||||||
if (number <= 0) {
|
if (number <= 0) {
|
||||||
throw new IllegalArgumentException("Number must be greater than 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;
|
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() {
|
public static int getMaxNumberOfStoredSignedPreKeys() {
|
||||||
return MAX_NUMBER_OF_STORED_SIGNED_PREKEYS;
|
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) {
|
public static void setAddOmemoHintBody(boolean addHint) {
|
||||||
ADD_OMEMO_HINT_BODY = 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() {
|
public static boolean getAddOmemoHintBody() {
|
||||||
return ADD_OMEMO_HINT_BODY;
|
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) {
|
public static void setFileBasedOmemoStoreDefaultPath(File path) {
|
||||||
FILE_BASED_OMEMO_STORE_DEFAULT_PATH = path;
|
FILE_BASED_OMEMO_STORE_DEFAULT_PATH = path;
|
||||||
}
|
}
|
||||||
|
@ -165,4 +186,44 @@ public final class OmemoConfiguration {
|
||||||
public static File getFileBasedOmemoStoreDefaultPath() {
|
public static File getFileBasedOmemoStoreDefaultPath() {
|
||||||
return FILE_BASED_OMEMO_STORE_DEFAULT_PATH;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ public abstract class OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
|
||||||
* @return decrypted message key.
|
* @return decrypted message key.
|
||||||
*
|
*
|
||||||
* @throws CorruptedOmemoKeyException
|
* @throws CorruptedOmemoKeyException
|
||||||
* @throws NoRawSessionException
|
* @throws NoRawSessionException when no double ratchet session was found.
|
||||||
* @throws CryptoFailedException
|
* @throws CryptoFailedException
|
||||||
* @throws UntrustedOmemoIdentityException
|
* @throws UntrustedOmemoIdentityException
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1087,20 +1087,23 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
OmemoMessage.Received decrypted;
|
OmemoMessage.Received decrypted;
|
||||||
|
BareJid sender;
|
||||||
|
|
||||||
MultiUserChat muc = getMuc(manager.getConnection(), stanza.getFrom());
|
try {
|
||||||
if (muc != null) {
|
MultiUserChat muc = getMuc(manager.getConnection(), stanza.getFrom());
|
||||||
Occupant occupant = muc.getOccupant(stanza.getFrom().asEntityFullJidIfPossible());
|
if (muc != null) {
|
||||||
Jid occupantJid = occupant.getJid();
|
Occupant occupant = muc.getOccupant(stanza.getFrom().asEntityFullJidIfPossible());
|
||||||
|
Jid occupantJid = occupant.getJid();
|
||||||
|
|
||||||
if (occupantJid == null) {
|
if (occupantJid == null) {
|
||||||
LOGGER.log(Level.WARNING, "MUC message received, but there is no way to retrieve the senders Jid. " +
|
LOGGER.log(Level.WARNING, "MUC message received, but there is no way to retrieve the senders Jid. " +
|
||||||
stanza.getFrom());
|
stanza.getFrom());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BareJid sender = occupantJid.asBareJid();
|
sender = occupantJid.asBareJid();
|
||||||
try {
|
|
||||||
|
// try is for this
|
||||||
decrypted = decryptMessage(managerGuard, sender, element, OmemoMessage.CARBON.NONE);
|
decrypted = decryptMessage(managerGuard, sender, element, OmemoMessage.CARBON.NONE);
|
||||||
|
|
||||||
if (element.isMessageElement()) {
|
if (element.isMessageElement()) {
|
||||||
|
@ -1108,14 +1111,10 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
|
||||||
} else {
|
} else {
|
||||||
manager.notifyOmemoMucKeyTransportMessageReceived(muc, stanza, decrypted);
|
manager.notifyOmemoMucKeyTransportMessageReceived(muc, stanza, decrypted);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
sender = stanza.getFrom().asBareJid();
|
||||||
|
|
||||||
} catch (CorruptedOmemoKeyException | CryptoFailedException | NoRawSessionException e) {
|
// and this
|
||||||
LOGGER.log(Level.WARNING, "Could not decrypt incoming message: ", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
BareJid sender = stanza.getFrom().asBareJid();
|
|
||||||
try {
|
|
||||||
decrypted = decryptMessage(managerGuard, sender, element, OmemoMessage.CARBON.NONE);
|
decrypted = decryptMessage(managerGuard, sender, element, OmemoMessage.CARBON.NONE);
|
||||||
|
|
||||||
if (element.isMessageElement()) {
|
if (element.isMessageElement()) {
|
||||||
|
@ -1123,10 +1122,49 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
|
||||||
} else {
|
} else {
|
||||||
manager.notifyOmemoKeyTransportMessageReceived(stanza, decrypted);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.omemo.exceptions;
|
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.
|
* 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;
|
private static final long serialVersionUID = 3466888654338119954L;
|
||||||
|
|
||||||
public NoRawSessionException(Exception e) {
|
private final OmemoDevice device;
|
||||||
|
|
||||||
|
public NoRawSessionException(OmemoDevice device, Exception e) {
|
||||||
super(e);
|
super(e);
|
||||||
|
this.device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OmemoDevice getDeviceWithoutSession() {
|
||||||
|
return device;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue