1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-09-12 18:49:39 +02:00

Reimplement encryption, solve compiler errors

This commit is contained in:
Paul Schaub 2018-01-05 14:49:15 +01:00
parent e5cdd89edc
commit 50f8a4f1fe
20 changed files with 497 additions and 234 deletions

View file

@ -29,6 +29,7 @@ import java.util.Random;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractConnectionListener;
@ -38,6 +39,7 @@ import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.NamedElement;
import org.jivesoftware.smack.packet.Stanza;
@ -52,22 +54,26 @@ import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.muc.MultiUserChatManager;
import org.jivesoftware.smackx.muc.RoomInfo;
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement;
import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement_VAxolotl;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
import org.jivesoftware.smackx.omemo.exceptions.NoOmemoSupportException;
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
import org.jivesoftware.smackx.omemo.listener.OmemoMucMessageListener;
import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.trust.TrustCallback;
import org.jivesoftware.smackx.omemo.trust.TrustState;
import org.jivesoftware.smackx.pep.PEPListener;
import org.jivesoftware.smackx.pep.PEPManager;
import org.jivesoftware.smackx.pubsub.EventElement;
import org.jivesoftware.smackx.pubsub.ItemsExtension;
import org.jivesoftware.smackx.pubsub.PayloadItem;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.packet.PubSub;
@ -245,7 +251,7 @@ public final class OmemoManager extends Manager {
*
* @param finishedCallback callback that gets called once the manager is initialized.
*/
public void initializeAsync(final FinishedCallback finishedCallback) {
public void initializeAsync(final InitializationFinishedCallback finishedCallback) {
Async.go(new Runnable() {
@Override
public void run() {
@ -287,8 +293,8 @@ public final class OmemoManager extends Manager {
* @throws SmackException.NoResponseException
*/
public OmemoMessage.Sent encrypt(BareJid recipient, String message)
throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException,
InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException,
throws CryptoFailedException, UndecidedOmemoIdentityException,
InterruptedException, SmackException.NotConnectedException,
SmackException.NoResponseException, SmackException.NotLoggedInException
{
synchronized (LOCK) {
@ -314,8 +320,8 @@ public final class OmemoManager extends Manager {
* @throws SmackException.NoResponseException
*/
public OmemoMessage.Sent encrypt(ArrayList<BareJid> recipients, String message)
throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException,
InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException,
throws CryptoFailedException, UndecidedOmemoIdentityException,
InterruptedException, SmackException.NotConnectedException,
SmackException.NoResponseException, SmackException.NotLoggedInException
{
synchronized (LOCK) {
@ -346,9 +352,9 @@ public final class OmemoManager extends Manager {
* with any of their devices.
*/
public OmemoMessage.Sent encrypt(MultiUserChat muc, String message)
throws UndecidedOmemoIdentityException, NoSuchAlgorithmException, CryptoFailedException,
throws UndecidedOmemoIdentityException, CryptoFailedException,
XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, NoOmemoSupportException, CannotEstablishOmemoSessionException,
SmackException.NoResponseException, NoOmemoSupportException,
SmackException.NotLoggedInException
{
synchronized (LOCK) {
@ -722,6 +728,10 @@ public final class OmemoManager extends Manager {
}
}
/**
* Set the deviceId of the manager to nDeviceId.
* @param nDeviceId new deviceId
*/
void setDeviceId(int nDeviceId) {
synchronized (LOCK) {
// Move this instance inside the HashMaps
@ -735,64 +745,58 @@ public final class OmemoManager extends Manager {
/**
* Notify all registered OmemoMessageListeners about a received OmemoMessage.
*
* @param decryptedBody decrypted Body element of the message
* @param encryptedMessage unmodified message as it was received
* @param wrappingMessage message that wrapped the incoming message
* @param messageInformation information about the messages encryption (used identityKey, carbon...)
* @param stanza original stanza
* @param decryptedMessage decrypted OmemoMessage.
*/
void notifyOmemoMessageReceived(String decryptedBody,
Message encryptedMessage,
Message wrappingMessage,
OmemoMessageInformation messageInformation)
void notifyOmemoMessageReceived(Stanza stanza, OmemoMessage.Received decryptedMessage)
{
for (OmemoMessageListener l : omemoMessageListeners) {
l.onOmemoMessageReceived(decryptedBody, encryptedMessage, wrappingMessage, messageInformation);
l.onOmemoMessageReceived(stanza, decryptedMessage);
}
}
void notifyOmemoKeyTransportMessageReceived(CipherAndAuthTag cipherAndAuthTag,
Message transportingMessage,
Message wrappingMessage,
OmemoMessageInformation information)
/**
* Notify all registered OmemoMessageListeners about a received OMEMO KeyTransportMessage.
*
* @param stanza original stanza.
* @param decryptedKeyTransportMessage decrypted KeyTransportMessage.
*/
void notifyOmemoKeyTransportMessageReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage)
{
for (OmemoMessageListener l : omemoMessageListeners) {
l.onOmemoKeyTransportReceived(cipherAndAuthTag, transportingMessage, wrappingMessage, information);
l.onOmemoKeyTransportReceived(stanza, decryptedKeyTransportMessage);
}
}
/**
* Notify all registered OmemoMucMessageListeners of an incoming OmemoMessageElement in a MUC.
*
* @param muc MultiUserChat the message was received in
* @param from BareJid of the user that sent the message
* @param decryptedBody decrypted body
* @param message original message with encrypted content
* @param wrappingMessage wrapping message (in case of carbon copy)
* @param omemoInformation information about the encryption of the message
* @param muc MultiUserChat the message was received in.
* @param stanza Original Stanza.
* @param decryptedMessage Decryped OmemoMessage.
*/
void notifyOmemoMucMessageReceived(MultiUserChat muc,
BareJid from,
String decryptedBody,
Message message,
Message wrappingMessage,
OmemoMessageInformation omemoInformation)
Stanza stanza,
OmemoMessage.Received decryptedMessage)
{
for (OmemoMucMessageListener l : omemoMucMessageListeners) {
l.onOmemoMucMessageReceived(muc, from, decryptedBody, message,
wrappingMessage, omemoInformation);
l.onOmemoMucMessageReceived(muc, stanza, decryptedMessage);
}
}
/**
* Notify registered OmemoMucMessageReceived listeners about KeyTransportMessages sent in a MUC.
*
* @param muc MultiUserChat in which the message was sent
* @param stanza original stanza
* @param decryptedKeyTransportMessage decrypted OMEMO KeyTransportElement
*/
void notifyOmemoMucKeyTransportMessageReceived(MultiUserChat muc,
BareJid from,
CipherAndAuthTag cipherAndAuthTag,
Message transportingMessage,
Message wrappingMessage,
OmemoMessageInformation messageInformation)
Stanza stanza,
OmemoMessage.Received decryptedKeyTransportMessage)
{
for (OmemoMucMessageListener l : omemoMucMessageListeners) {
l.onOmemoKeyTransportReceived(muc, from, cipherAndAuthTag,
transportingMessage, wrappingMessage, messageInformation);
l.onOmemoKeyTransportReceived(muc, stanza, decryptedKeyTransportMessage);
}
}
@ -808,10 +812,10 @@ public final class OmemoManager extends Manager {
// Remove listeners to avoid them getting added twice
connection().removeAsyncStanzaListener(internalOmemoMessageStanzaListener);
carbonManager.removeCarbonCopyReceivedListener(internalOmemoCarbonCopyListener);
//pepManager.removePEPListener(deviceListUpdateListener);
pepManager.removePEPListener(deviceListUpdateListener);
// Add listeners
//pepManager.addPEPListener(deviceListUpdateListener);
pepManager.addPEPListener(deviceListUpdateListener);
connection().addAsyncStanzaListener(internalOmemoMessageStanzaListener, omemoMessageStanzaFilter);
carbonManager.addCarbonCopyReceivedListener(internalOmemoCarbonCopyListener);
}
@ -820,7 +824,7 @@ public final class OmemoManager extends Manager {
* Remove active stanza listeners needed for OMEMO.
*/
public void stopListeners() {
//PEPManager.getInstanceFor(connection()).removePEPListener(deviceListUpdateListener);
PEPManager.getInstanceFor(connection()).removePEPListener(deviceListUpdateListener);
connection().removeAsyncStanzaListener(internalOmemoMessageStanzaListener);
CarbonManager.getInstanceFor(connection()).removeCarbonCopyReceivedListener(internalOmemoCarbonCopyListener);
}
@ -844,6 +848,9 @@ public final class OmemoManager extends Manager {
return service;
}
/**
* StanzaListener that listens for incoming Stanzas which contain OMEMO elements.
*/
private final StanzaListener internalOmemoMessageStanzaListener = new StanzaListener() {
@Override
public void processStanza(Stanza packet) throws SmackException.NotConnectedException, InterruptedException {
@ -856,6 +863,9 @@ public final class OmemoManager extends Manager {
}
};
/**
* CarbonCopyListener that listens for incoming carbon copies which contain OMEMO elements.
*/
private final CarbonCopyReceivedListener internalOmemoCarbonCopyListener = new CarbonCopyReceivedListener() {
@Override
public void onCarbonCopyReceived(CarbonExtension.Direction direction,
@ -872,6 +882,64 @@ public final class OmemoManager extends Manager {
}
};
/**
* PEPListener that listens for OMEMO deviceList updates.
*/
private final PEPListener deviceListUpdateListener = new PEPListener() {
@Override
public void eventReceived(EntityBareJid from, EventElement event, Message message) {
// Unknown sender, no more work to do.
if (from == null) {
// TODO: This DOES happen for some reason. Figure out when...
return;
}
for (ExtensionElement items : event.getExtensions()) {
if (!(items instanceof ItemsExtension)) {
continue;
}
for (ExtensionElement item : ((ItemsExtension) items).getItems()) {
if (!(item instanceof PayloadItem<?>)) {
continue;
}
PayloadItem<?> payloadItem = (PayloadItem<?>) item;
if (!(payloadItem.getPayload() instanceof OmemoDeviceListElement)) {
continue;
}
// Device List <list>
OmemoDeviceListElement receivedDeviceList = (OmemoDeviceListElement) payloadItem.getPayload();
getOmemoService().getOmemoStoreBackend().mergeCachedDeviceList(getOwnDevice(), from, receivedDeviceList);
if (!from.asBareJid().equals(getOwnJid())) {
continue;
}
OmemoCachedDeviceList deviceList = getOmemoService().cleanUpDeviceList(getOwnDevice());
final OmemoDeviceListElement_VAxolotl newDeviceList = new OmemoDeviceListElement_VAxolotl(deviceList);
if (!newDeviceList.equals(receivedDeviceList)) {
Async.go(new Runnable() {
@Override
public void run() {
try {
OmemoService.publishDeviceList(connection(), newDeviceList);
} catch (InterruptedException | XMPPException.XMPPErrorException |
SmackException.NotConnectedException | SmackException.NoResponseException e) {
LOGGER.log(Level.WARNING, "Could not publish our deviceList upon an received update.", e);
}
}
});
}
}
}
}
};
/**
* StanzaFilter that filters messages containing a OMEMO element.
*/
@ -882,6 +950,9 @@ public final class OmemoManager extends Manager {
}
};
/**
* Guard class which ensures that the wrapped OmemoManager knows its BareJid.
*/
public static class LoggedInOmemoManager {
private final OmemoManager manager;
@ -909,13 +980,23 @@ public final class OmemoManager extends Manager {
}
}
public interface FinishedCallback {
/**
* Callback which can be used to get notified, when the OmemoManager finished initializing.
*/
public interface InitializationFinishedCallback {
void initializationFinished(OmemoManager manager);
void initializationFailed(Exception cause);
}
/**
* Get the bareJid of the user from the authenticated XMPP connection.
* If our deviceId is unknown, use the bareJid to look up deviceIds available in the omemoStore.
* If there are ids available, choose the smallest one. Otherwise generate a random deviceId.
*
* @param manager OmemoManager
*/
private static void initBareJidAndDeviceId(OmemoManager manager) {
if (!manager.getConnection().isAuthenticated()) {
throw new IllegalStateException("Connection MUST be authenticated.");

View file

@ -34,21 +34,32 @@ import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
public class OmemoMessage {
private final OmemoElement element;
private final byte[] messageKey, iv;
OmemoMessage(OmemoElement element) {
OmemoMessage(OmemoElement element, byte[] key, byte[] iv) {
this.element = element;
this.messageKey = key;
this.iv = iv;
}
public OmemoElement getElement() {
return element;
}
public byte[] getKey() {
return messageKey.clone();
}
public byte[] getIv() {
return iv.clone();
}
public static class Sent extends OmemoMessage {
private final ArrayList<OmemoDevice> intendedDevices = new ArrayList<>();
private final HashMap<OmemoDevice, Throwable> skippedDevices = new HashMap<>();
Sent(OmemoElement element, List<OmemoDevice> intendedDevices, HashMap<OmemoDevice, Throwable> skippedDevices) {
super(element);
Sent(OmemoElement element, byte[] key, byte[] iv, List<OmemoDevice> intendedDevices, HashMap<OmemoDevice, Throwable> skippedDevices) {
super(element, key, iv);
this.intendedDevices.addAll(intendedDevices);
this.skippedDevices.putAll(skippedDevices);
}
@ -86,13 +97,15 @@ public class OmemoMessage {
private final OmemoFingerprint sendersFingerprint;
private final OmemoDevice senderDevice;
private final CARBON carbon;
private final boolean preKeyMessage;
Received(OmemoElement element, String message, OmemoFingerprint sendersFingerprint, OmemoDevice senderDevice, CARBON carbon) {
super(element);
Received(OmemoElement element, byte[] key, byte[] iv, String message, OmemoFingerprint sendersFingerprint, OmemoDevice senderDevice, CARBON carbon, boolean preKeyMessage) {
super(element, key, iv);
this.message = message;
this.sendersFingerprint = sendersFingerprint;
this.senderDevice = senderDevice;
this.carbon = carbon;
this.preKeyMessage = preKeyMessage;
}
public String getMessage() {
@ -115,6 +128,10 @@ public class OmemoMessage {
public CARBON getCarbon() {
return carbon;
}
boolean isPreKeyMessage() {
return preKeyMessage;
}
}
/**

View file

@ -24,7 +24,6 @@ import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.element.OmemoKeyElement;
@ -43,16 +42,41 @@ public abstract class OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
protected final OmemoManager omemoManager;
protected final OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store;
/**
* Constructor.
*
* @param omemoManager omemoManager
* @param store omemoStore
*/
public OmemoRatchet(OmemoManager omemoManager,
OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store) {
this.omemoManager = omemoManager;
this.store = store;
}
/**
* Decrypt a double-ratchet-encrypted message key.
*
* @param sender sender of the message.
* @param encryptedKey key encrypted with the ratchet of the sender.
* @return decrypted message key.
*
* @throws CorruptedOmemoKeyException
* @throws NoRawSessionException
* @throws CryptoFailedException
* @throws UntrustedOmemoIdentityException
*/
public abstract byte[] doubleRatchetDecrypt(OmemoDevice sender, byte[] encryptedKey)
throws CorruptedOmemoKeyException, NoRawSessionException, CryptoFailedException,
UntrustedOmemoIdentityException;
/**
* Encrypt a messageKey with the double ratchet session of the recipient.
*
* @param recipient recipient of the message.
* @param messageKey key we want to encrypt.
* @return encrypted message key.
*/
public abstract CiphertextTuple doubleRatchetEncrypt(OmemoDevice recipient, byte[] messageKey);
/**
@ -70,11 +94,14 @@ public abstract class OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
List<CryptoFailedException> decryptExceptions = new ArrayList<>();
List<OmemoKeyElement> keys = element.getHeader().getKeys();
boolean preKey = false;
// Find key with our ID.
for (OmemoKeyElement k : keys) {
if (k.getId() == keyId) {
try {
unpackedKey = doubleRatchetDecrypt(sender, k.getData());
preKey = k.isPreKey();
break;
} catch (CryptoFailedException e) {
// There might be multiple keys with our id, but we can only decrypt one.
@ -114,7 +141,7 @@ public abstract class OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
+ unpackedKey.length + ". Probably legacy auth tag format.");
}
return new CipherAndAuthTag(messageKey, element.getHeader().getIv(), authTag);
return new CipherAndAuthTag(messageKey, element.getHeader().getIv(), authTag, preKey);
}
/**
@ -123,10 +150,12 @@ public abstract class OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
*
* @param element omemoElement containing a payload.
* @param cipherAndAuthTag cipher and authentication tag.
* @return Message containing the decrypted payload in its body.
* @throws CryptoFailedException
* @return decrypted plain text.
* @throws CryptoFailedException if decryption using AES key fails.
*/
static Message decryptMessageElement(OmemoElement element, CipherAndAuthTag cipherAndAuthTag) throws CryptoFailedException {
static String decryptMessageElement(OmemoElement element, CipherAndAuthTag cipherAndAuthTag)
throws CryptoFailedException
{
if (!element.isMessageElement()) {
throw new IllegalArgumentException("decryptMessageElement cannot decrypt OmemoElement which is no MessageElement!");
}
@ -140,9 +169,7 @@ public abstract class OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
try {
String plaintext = new String(cipherAndAuthTag.getCipher().doFinal(encryptedBody), StringUtils.UTF8);
Message decrypted = new Message();
decrypted.setBody(plaintext);
return decrypted;
return plaintext;
} catch (UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException e) {
throw new CryptoFailedException("decryptMessageElement could not decipher message body: "

View file

@ -32,6 +32,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
@ -45,17 +46,23 @@ import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.muc.MultiUserChatManager;
import org.jivesoftware.smackx.muc.Occupant;
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement;
import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement_VAxolotl;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.element.OmemoElement_VAxolotl;
import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
import org.jivesoftware.smackx.omemo.exceptions.NoIdentityKeyException;
import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
import org.jivesoftware.smackx.omemo.exceptions.StaleDeviceException;
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.listener.OmemoCarbonCopyStanzaReceivedListener;
@ -72,6 +79,8 @@ import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.Jid;
/**
* This class contains OMEMO related logic and registers listeners etc.
@ -103,8 +112,8 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
*/
private static OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> INSTANCE;
protected OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore;
protected final HashMap<OmemoManager, OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>> omemoRatchets = new HashMap<>();
private OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore;
private final HashMap<OmemoManager, OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>> omemoRatchets = new HashMap<>();
/**
* Create a new OmemoService object. This should only happen once.
@ -120,10 +129,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @throws BadPaddingException when cipher.doFinal gets wrong padding
* @throws IllegalBlockSizeException when cipher.doFinal gets wrong Block size.
*/
protected OmemoService()
throws NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, IllegalBlockSizeException,
BadPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException
{
protected OmemoService() {
// TODO
}
@ -178,6 +184,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
*
* @param omemoStore store.
*/
@SuppressWarnings("unused")
public void setOmemoStoreBackend(
OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore) {
if (this.omemoStore != null) {
@ -342,11 +349,11 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @throws UndecidedOmemoIdentityException if the list of recipient devices contains undecided devices
* @throws CryptoFailedException if we are lacking some crypto primitives
*/
OmemoMessage.Sent encrypt(OmemoManager.LoggedInOmemoManager managerGuard,
List<OmemoDevice> contactsDevices,
byte[] messageKey,
byte[] iv,
String message)
private OmemoMessage.Sent encrypt(OmemoManager.LoggedInOmemoManager managerGuard,
List<OmemoDevice> contactsDevices,
byte[] messageKey,
byte[] iv,
String message)
throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException,
UndecidedOmemoIdentityException, CryptoFailedException
{
@ -418,23 +425,72 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
OmemoElement element = builder.finish();
return new OmemoMessage.Sent(element, contactsDevices, skippedRecipients);
return new OmemoMessage.Sent(element, messageKey, iv, contactsDevices, skippedRecipients);
}
/**
* Decrypt an OMEMO message.
* @param managerGuard authenticated OmemoManager.
* @param senderJid BareJid of the sender.
* @param omemoElement omemoElement.
* @return decrypted OmemoMessage object.
*
* @throws CorruptedOmemoKeyException if the identityKey of the sender is damaged.
* @throws CryptoFailedException if decryption fails.
* @throws NoRawSessionException if we have no session with the device and it sent a normal (non-preKey) message.
*/
private OmemoMessage.Received decryptMessage(OmemoManager.LoggedInOmemoManager managerGuard,
BareJid senderJid,
OmemoElement omemoElement,
OmemoMessage.CARBON carbon)
throws CorruptedOmemoKeyException, CryptoFailedException, NoRawSessionException
{
OmemoManager manager = managerGuard.get();
int senderId = omemoElement.getHeader().getSid();
OmemoDevice senderDevice = new OmemoDevice(senderJid, senderId);
CipherAndAuthTag cipherAndAuthTag = getOmemoRatchet(manager)
.retrieveMessageKeyAndAuthTag(senderDevice, omemoElement);
// Retrieve senders fingerprint. TODO: Find a way to do this without the store.
OmemoFingerprint senderFingerprint;
try {
senderFingerprint = getOmemoStoreBackend().getFingerprint(manager.getOwnDevice(), senderDevice);
} catch (NoIdentityKeyException e) {
throw new AssertionError("Cannot retrieve OmemoFingerprint of sender although decryption was successful: " + e);
}
if (omemoElement.isMessageElement()) {
// Use symmetric message key to decrypt message payload.
String plaintext = OmemoRatchet.decryptMessageElement(omemoElement, cipherAndAuthTag);
return new OmemoMessage.Received(omemoElement, cipherAndAuthTag.getKey(), cipherAndAuthTag.getIv(),
plaintext, senderFingerprint, senderDevice, carbon, cipherAndAuthTag.wasPreKeyEncrypted());
} else {
// KeyTransportMessages don't require decryption of the payload.
return new OmemoMessage.Received(omemoElement, cipherAndAuthTag.getKey(), cipherAndAuthTag.getIv(),
null, senderFingerprint, senderDevice, carbon, cipherAndAuthTag.wasPreKeyEncrypted());
}
}
/**
* Create an OMEMO KeyTransportElement.
* @see <a href="https://xmpp.org/extensions/xep-0384.html#usecases-keysend">XEP-0384: Sending a key</a>.
*
* @param managerGuard Initialized OmemoManager.
* @param contactsDevices recipient devices.
* @param key AES-Key to be transported.
* @param iv initialization vector to be used with the key.
* @return KeyTransportElement
*
* @param managerGuard
* @param contactsDevices
* @param key
* @param iv
* @return
* @throws InterruptedException
* @throws UndecidedOmemoIdentityException if the list of recipients contains an undecided device
* @throws CryptoFailedException if we are lacking some cryptographic algorithms
* @throws SmackException.NotConnectedException
* @throws SmackException.NoResponseException
*/
OmemoMessage.Sent createKeyTransportMessage(OmemoManager.LoggedInOmemoManager managerGuard,
OmemoMessage.Sent createKeyTransportElement(OmemoManager.LoggedInOmemoManager managerGuard,
List<OmemoDevice> contactsDevices,
byte[] key,
byte[] iv)
@ -570,14 +626,24 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @throws SmackException.NoResponseException
* @throws PubSubException.NotALeafNodeException
*/
private static void publishDeviceList(XMPPConnection connection, OmemoDeviceListElement deviceList)
static void publishDeviceList(XMPPConnection connection, OmemoDeviceListElement deviceList)
throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
SmackException.NoResponseException, PubSubException.NotALeafNodeException
SmackException.NoResponseException
{
PubSubManager.getInstance(connection, connection.getUser().asBareJid())
.tryToPublishAndPossibleAutoCreate(OmemoConstants.PEP_NODE_DEVICE_LIST, new PayloadItem<>(deviceList));
}
/**
*
* @param connection
* @param userDevice
* @throws InterruptedException
* @throws PubSubException.NotALeafNodeException
* @throws XMPPException.XMPPErrorException
* @throws SmackException.NotConnectedException
* @throws SmackException.NoResponseException
*/
private void refreshAndRepublishDeviceList(XMPPConnection connection, OmemoDevice userDevice)
throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException
@ -592,21 +658,10 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
if (publishedList == null) {
publishedList = new OmemoDeviceListElement_VAxolotl(Collections.<Integer>emptySet());
}
OmemoCachedDeviceList cachedList = getOmemoStoreBackend().mergeCachedDeviceList(
userDevice, userDevice.getJid(), publishedList);
getOmemoStoreBackend().mergeCachedDeviceList(userDevice, userDevice.getJid(), publishedList);
// Delete stale devices if allowed and necessary
if (OmemoConfiguration.getDeleteStaleDevices()) {
cachedList = deleteStaleDevices(userDevice);
}
// Add back our device if necessary
if (!cachedList.getActiveDevices().contains(userDevice.getDeviceId())) {
cachedList.addDevice(userDevice.getDeviceId());
}
getOmemoStoreBackend().storeCachedDeviceList(userDevice, userDevice.getJid(), cachedList);
OmemoCachedDeviceList cachedList = cleanUpDeviceList(userDevice);
// Republish our deviceId if it is missing from the published list.
if (!publishedList.getDeviceIds().equals(cachedList.getActiveDevices())) {
@ -614,6 +669,33 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
}
}
/**
* Add our load the deviceList of the user from cache, delete stale devices if needed, add the users device
* back if necessary, store the refurbished list in cache and return it.
*
* @param userDevice
* @return
*/
OmemoCachedDeviceList cleanUpDeviceList(OmemoDevice userDevice) {
OmemoCachedDeviceList cachedDeviceList;
// Delete stale devices if allowed and necessary
if (OmemoConfiguration.getDeleteStaleDevices()) {
cachedDeviceList = deleteStaleDevices(userDevice);
} else {
cachedDeviceList = getOmemoStoreBackend().loadCachedDeviceList(userDevice);
}
// Add back our device if necessary
if (!cachedDeviceList.getActiveDevices().contains(userDevice.getDeviceId())) {
cachedDeviceList.addDevice(userDevice.getDeviceId());
}
getOmemoStoreBackend().storeCachedDeviceList(userDevice, userDevice.getJid(), cachedDeviceList);
return cachedDeviceList;
}
/**
* Refresh and merge device list of contact.
*
@ -831,7 +913,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @param contactsDevice OmemoDevice of the contact.
* @return true if userDevice has session with contactsDevice.
*/
boolean hasSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
private boolean hasSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
return getOmemoStoreBackend().loadRawSession(userDevice, contactsDevice) != null;
}
@ -948,7 +1030,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @param userDevice our OmemoDevice
* @param devices collection of OmemoDevices
*/
static void removeOurDevice(OmemoDevice userDevice, Collection<OmemoDevice> devices) {
private static void removeOurDevice(OmemoDevice userDevice, Collection<OmemoDevice> devices) {
if (devices.contains(userDevice)) {
devices.remove(userDevice);
}
@ -996,7 +1078,74 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
}
@Override
public void onOmemoMessageStanzaReceived(Stanza stanza, OmemoManager.LoggedInOmemoManager omemoManager) {
// TODO: Implement
public void onOmemoMessageStanzaReceived(Stanza stanza, OmemoManager.LoggedInOmemoManager managerGuard) {
OmemoManager manager = managerGuard.get();
OmemoElement element = stanza.getExtension(OmemoElement.NAME_ENCRYPTED, OmemoElement_VAxolotl.NAMESPACE);
if (element == null) {
return;
}
OmemoMessage.Received decrypted;
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;
}
BareJid sender = occupantJid.asBareJid();
try {
decrypted = decryptMessage(managerGuard, sender, element, OmemoMessage.CARBON.NONE);
if (element.isMessageElement()) {
manager.notifyOmemoMucMessageReceived(muc, stanza, decrypted);
} else {
manager.notifyOmemoMucKeyTransportMessageReceived(muc, stanza, decrypted);
}
} catch (CorruptedOmemoKeyException | CryptoFailedException | NoRawSessionException e) {
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);
if (element.isMessageElement()) {
manager.notifyOmemoMessageReceived(stanza, decrypted);
} else {
manager.notifyOmemoKeyTransportMessageReceived(stanza, decrypted);
}
} catch (CorruptedOmemoKeyException | CryptoFailedException | NoRawSessionException e) {
LOGGER.log(Level.WARNING, "Could not decrypt incoming message: ", e);
}
}
}
/**
* Return the joined MUC with EntityBareJid jid, or null if its not a room and/or not joined.
* @param connection xmpp connection
* @param jid jid (presumably) of the MUC
* @return MultiUserChat or null if not a MUC.
*/
private static MultiUserChat getMuc(XMPPConnection connection, Jid jid) {
EntityBareJid ebj = jid.asEntityBareJidIfPossible();
if (ebj == null) {
return null;
}
MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(connection);
Set<EntityBareJid> joinedRooms = mucm.getJoinedRooms();
if (joinedRooms.contains(ebj)) {
return mucm.getMultiUserChat(ebj);
}
return null;
}
}

View file

@ -25,6 +25,8 @@ import static org.jivesoftware.smackx.omemo.util.OmemoConstants.OMEMO_NAMESPACE_
*/
public class OmemoElement_VAxolotl extends OmemoElement {
public static final String NAMESPACE = OMEMO_NAMESPACE_V_AXOLOTL;
/**
* Create a new OmemoMessageElement from a header and a payload.
*
@ -37,6 +39,6 @@ public class OmemoElement_VAxolotl extends OmemoElement {
@Override
public String getNamespace() {
return OMEMO_NAMESPACE_V_AXOLOTL;
return NAMESPACE;
}
}

View file

@ -1,3 +1,19 @@
/**
*
* Copyright 2017 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.omemo.element;
import org.jivesoftware.smack.packet.NamedElement;

View file

@ -78,4 +78,16 @@ public class UntrustedOmemoIdentityException extends Exception {
public OmemoFingerprint getUntrustedFingerprint() {
return untrustedKey;
}
@Override
public String toString() {
if (trustedKey != null) {
return "Untrusted OMEMO Identity encountered:\n" +
"Fingerprint of trusted key:\n" + trustedKey.blocksOf8Chars() + "\n" +
"Fingerprint of untrusted key:\n" + untrustedKey.blocksOf8Chars();
} else {
return "Untrusted OMEMO Identity encountered:\n" +
"Fingerprint of untrusted key:\n" + untrustedKey.blocksOf8Chars();
}
}
}

View file

@ -23,7 +23,6 @@ import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.PROVIDER;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
@ -38,11 +37,13 @@ import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
*/
public class CipherAndAuthTag {
private final byte[] key, iv, authTag;
private final boolean wasPreKey;
public CipherAndAuthTag(byte[] key, byte[] iv, byte[] authTag) throws CryptoFailedException {
public CipherAndAuthTag(byte[] key, byte[] iv, byte[] authTag, boolean wasPreKey) {
this.authTag = authTag;
this.key = key;
this.iv = iv;
this.wasPreKey = wasPreKey;
}
public Cipher getCipher() throws CryptoFailedException {
@ -82,4 +83,8 @@ public class CipherAndAuthTag {
}
return null;
}
public boolean wasPreKeyEncrypted() {
return wasPreKey;
}
}

View file

@ -16,10 +16,8 @@
*/
package org.jivesoftware.smackx.omemo.listener;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.omemo.OmemoMessage;
/**
* Listener interface that allows implementations to receive decrypted OMEMO messages.
@ -30,20 +28,16 @@ public interface OmemoMessageListener {
/**
* Gets called, whenever an OmemoMessage has been received and was successfully decrypted.
*
* @param decryptedBody Decrypted body
* @param encryptedMessage Encrypted Message
* @param wrappingMessage Wrapping carbon message, in case the message was a carbon copy, else null.
* @param omemoInformation Information about the messages encryption etc.
* @param stanza Received (encrypted) stanza.
* @param decryptedMessage decrypted OmemoMessage.
*/
void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation);
void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received decryptedMessage);
/**
* Gets called, whenever an OmemoElement without a body (an OmemoKeyTransportElement) is received.
*
* @param cipherAndAuthTag transported Cipher along with an optional AuthTag
* @param message Message that contained the KeyTransport
* @param wrappingMessage Wrapping message (eg. carbon), or null
* @param omemoInformation Information about the messages encryption etc.
* @param stanza received (encrypted) stanza.
* @param decryptedKeyTransportMessage decrypted OMEMO key transport message.
*/
void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation);
void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage);
}

View file

@ -16,13 +16,9 @@
*/
package org.jivesoftware.smackx.omemo.listener;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.jxmpp.jid.BareJid;
import org.jivesoftware.smackx.omemo.OmemoMessage;
/**
* Listener interface that allows implementations to receive decrypted OMEMO MUC messages.
@ -33,24 +29,17 @@ public interface OmemoMucMessageListener {
/**
* Gets called whenever an OMEMO message has been received in a MultiUserChat and successfully decrypted.
* @param muc MultiUserChat the message was sent in
* @param from the bareJid of the sender
* @param decryptedBody the decrypted Body of the message
* @param message the original message with encrypted element
* @param wrappingMessage in case of a carbon copy, this is the wrapping message
* @param omemoInformation information about the encryption of the message
* @param stanza Original Stanza
* @param decryptedOmemoMessage decrypted Omemo message
*/
void onOmemoMucMessageReceived(MultiUserChat muc, BareJid from, String decryptedBody, Message message,
Message wrappingMessage, OmemoMessageInformation omemoInformation);
void onOmemoMucMessageReceived(MultiUserChat muc, Stanza stanza, OmemoMessage.Received decryptedOmemoMessage);
/**
* Gets called, whenever an OmemoElement without a body (an OmemoKeyTransportElement) is received.
*
* @param muc MultiUserChat the message was sent in
* @param from bareJid of the sender
* @param cipherAndAuthTag transportedKey along with an optional authTag
* @param message Message that contained the KeyTransport
* @param wrappingMessage Wrapping message (eg. carbon), or null
* @param omemoInformation Information about the messages encryption etc.
* @param muc MultiUserChat the message was sent in
* @param stanza original stanza
* @param decryptedKeyTransportElement decrypted KeyTransportMessage
*/
void onOmemoKeyTransportReceived(MultiUserChat muc, BareJid from, CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation);
void onOmemoKeyTransportReceived(MultiUserChat muc, Stanza stanza, OmemoMessage.Received decryptedKeyTransportElement);
}

View file

@ -21,8 +21,6 @@ import static org.junit.Assert.assertNull;
import java.io.File;
import org.jivesoftware.smackx.omemo.OmemoConfiguration;
import junit.framework.TestCase;
import org.junit.Test;

View file

@ -61,11 +61,12 @@ public class WrapperObjectsTest {
byte[] iv = OmemoMessageBuilder.generateIv();
byte[] authTag = OmemoMessageBuilder.generateIv();
CipherAndAuthTag cat = new CipherAndAuthTag(key, iv, authTag);
CipherAndAuthTag cat = new CipherAndAuthTag(key, iv, authTag, true);
assertNotNull(cat.getCipher());
assertArrayEquals(key, cat.getKey());
assertArrayEquals(iv, cat.getIv());
assertArrayEquals(authTag, cat.getAuthTag());
assertTrue(cat.wasPreKeyEncrypted());
}
}