mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-05 19:55:59 +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);
|
||||
}
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue