1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-11-23 20:42:06 +01: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

@ -64,7 +64,7 @@ public abstract class AbstractTwoUsersOmemoIntegrationTest extends AbstractOmemo
}
@AfterClass
public void cleanUp() throws SmackException.NotLoggedInException {
public void cleanUp() {
alice.stopListeners();
bob.stopListeners();
OmemoManagerSetupHelper.cleanUpPubSub(alice);

View file

@ -22,9 +22,8 @@ import static org.junit.Assert.assertFalse;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
@ -51,13 +50,13 @@ public class MessageEncryptionIntegrationTest extends AbstractTwoUsersOmemoInteg
OmemoBundleElement bobsBundle1 = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice());
final String message1 = "One is greater than zero (for small values of zero).";
Message encrypted1 = alice.encrypt(bob.getOwnJid(), message1);
OmemoMessage.Sent encrypted1 = alice.encrypt(bob.getOwnJid(), message1);
final SimpleResultSyncPoint bobReceivedMessage = new SimpleResultSyncPoint();
bob.addOmemoMessageListener(new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
if (decryptedBody.equals(message1)) {
public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) {
if (received.getMessage().equals(message1)) {
bobReceivedMessage.signal();
} else {
bobReceivedMessage.signalFailure("Received decrypted message was not equal to sent message.");
@ -65,13 +64,16 @@ public class MessageEncryptionIntegrationTest extends AbstractTwoUsersOmemoInteg
}
@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
// Not used
public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage) {
// Not needed
}
});
encrypted1.setTo(bob.getOwnJid());
alice.getConnection().sendStanza(encrypted1);
Message m1 = new Message();
m1.addExtension(encrypted1.getElement());
m1.setTo(bob.getOwnJid());
alice.getConnection().sendStanza(m1);
bobReceivedMessage.waitForResult(10 * 1000);
OmemoBundleElement aliceBundle2 = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice());
@ -82,13 +84,13 @@ public class MessageEncryptionIntegrationTest extends AbstractTwoUsersOmemoInteg
assertFalse(bobsBundle1.equals(bobsBundle2));
final String message2 = "The german words for 'leek' and 'wimp' are the same.";
final Message encrypted2 = bob.encrypt(alice.getOwnJid(), message2);
final OmemoMessage.Sent encrypted2 = bob.encrypt(alice.getOwnJid(), message2);
final SimpleResultSyncPoint aliceReceivedMessage = new SimpleResultSyncPoint();
alice.addOmemoMessageListener(new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
if (decryptedBody.equals(message2)) {
public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) {
if (received.getMessage().equals(message2)) {
aliceReceivedMessage.signal();
} else {
aliceReceivedMessage.signalFailure("Received decrypted message was not equal to sent message.");
@ -96,13 +98,15 @@ public class MessageEncryptionIntegrationTest extends AbstractTwoUsersOmemoInteg
}
@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
// Not needed here either.
public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage) {
// Not needed
}
});
encrypted2.setTo(alice.getOwnJid());
bob.getConnection().sendStanza(encrypted2);
Message m2 = new Message();
m2.addExtension(encrypted2.getElement());
m2.setTo(alice.getOwnJid());
bob.getConnection().sendStanza(m2);
aliceReceivedMessage.waitForResult(10 * 1000);
OmemoBundleElement aliceBundle3 = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice());

View file

@ -25,7 +25,6 @@ import java.util.HashMap;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.omemo.util.EphemeralTrustCallback;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.roster.PresenceEventListener;
import org.jivesoftware.smack.roster.Roster;
@ -35,6 +34,7 @@ import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.util.EphemeralTrustCallback;
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.PubSubManager;
@ -45,6 +45,7 @@ import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.FullJid;
import org.jxmpp.jid.Jid;
public class OmemoManagerSetupHelper {
/**
@ -177,14 +178,11 @@ public class OmemoManagerSetupHelper {
}
}
public static void cleanUpPubSub(OmemoManager omemoManager)
throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
PubSubException.NotALeafNodeException
{
public static void cleanUpPubSub(OmemoManager omemoManager) {
PubSubManager pm = PubSubManager.getInstance(omemoManager.getConnection(),omemoManager.getOwnJid());
try {
omemoManager.requestDeviceListUpdateFor(omemoManager.getOwnJid());
} catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException e) {
} catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException e) {
// ignore
}

View file

@ -23,8 +23,7 @@ import java.util.concurrent.TimeoutException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
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.listener.OmemoMessageListener;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
@ -41,16 +40,16 @@ public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIn
super(environment);
}
private static final String m1 = "P = NP is true for all N,P from the set of complex numbers, where P is equal to 0";
private static final String body1 = "P = NP is true for all N,P from the set of complex numbers, where P is equal to 0";
private final SimpleResultSyncPoint bsp1 = new SimpleResultSyncPoint();
private final OmemoMessageListener bml1 = new OmemoTestMessageListener(m1, bsp1);
private final OmemoMessageListener bml1 = new OmemoTestMessageListener(body1, bsp1);
private static final String m2 = "P = NP is also true for all N,P from the set of complex numbers, where N is equal to 1.";
private static final String body2 = "P = NP is also true for all N,P from the set of complex numbers, where N is equal to 1.";
private final ResultSyncPoint<Boolean, IllegalStateException> bsp2 = new ResultSyncPoint<>();
private final OmemoMessageListener bml2 = new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
if (decryptedBody.equals(m2)) {
public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) {
if (received.getMessage().equals(body2)) {
bsp2.signal(new IllegalStateException("Message MUST NOT be decryptable!"));
} else {
bsp2.signal(new IllegalStateException("OmemoMessageListener MUST NOT be called for this message."));
@ -58,37 +57,40 @@ public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIn
}
@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage) {
// Not needed
}
};
private final SimpleResultSyncPoint asp2 = new SimpleResultSyncPoint();
private final OmemoMessageListener aml2 = new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
@Override
public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received decryptedMessage) {
// Not needed
}
@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received received) {
asp2.signal();
}
};
private static final String m3 = "P = NP would be a disaster for the world of cryptography.";
private static final String body3 = "P = NP would be a disaster for the world of cryptography.";
private final SimpleResultSyncPoint bsp3 = new SimpleResultSyncPoint();
private final OmemoMessageListener bml3 = new OmemoTestMessageListener(m3, bsp3);
private final OmemoMessageListener bml3 = new OmemoTestMessageListener(body3, bsp3);
@SmackIntegrationTest
public void sessionRenegotiationTest() throws Exception {
/*
Send (PreKey-)message from Alice to Bob to initiate a session.
*/
Message e1 = alice.encrypt(bob.getOwnJid(), m1);
e1.setTo(bob.getOwnJid());
OmemoMessage.Sent e1 = alice.encrypt(bob.getOwnJid(), body1);
Message m1 = new Message();
m1.addExtension(e1.getElement());
m1.setTo(bob.getOwnJid());
bob.addOmemoMessageListener(bml1);
alice.getConnection().sendStanza(e1);
alice.getConnection().sendStanza(m1);
bsp1.waitForResult(10 * 1000);
bob.removeOmemoMessageListener(bml1);
@ -100,12 +102,14 @@ public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIn
/*
Send normal message from Alice to Bob (Alice assumes, that Bob still has a valid session).
*/
Message e2 = alice.encrypt(bob.getOwnJid(), m2);
e2.setTo(bob.getOwnJid());
OmemoMessage.Sent e2 = alice.encrypt(bob.getOwnJid(), body2);
Message m2 = new Message();
m2.addExtension(e2.getElement());
m2.setTo(bob.getOwnJid());
bob.addOmemoMessageListener(bml2);
alice.addOmemoMessageListener(aml2);
alice.getConnection().sendStanza(e2);
alice.getConnection().sendStanza(m2);
/*
Wait for the timeout on Bobs side, since message decryption will fail now.
@ -127,11 +131,13 @@ public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIn
Since Bob responded with a PreKeyMessage to repair the broken session, Alice should now be able to send messages
which Bob can decrypt successfully again.
*/
Message e3 = alice.encrypt(bob.getOwnJid(), m3);
e3.setTo(bob.getOwnJid());
OmemoMessage.Sent e3 = alice.encrypt(bob.getOwnJid(), body3);
Message m3 = new Message();
m3.addExtension(e3.getElement());
m3.setTo(bob.getOwnJid());
bob.addOmemoMessageListener(bml3);
alice.getConnection().sendStanza(e3);
alice.getConnection().sendStanza(m3);
bsp3.waitForResult(10 * 1000);
bob.removeOmemoMessageListener(bml3);
}
@ -147,8 +153,8 @@ public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIn
}
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
if (decryptedBody.equals(expectedMessage)) {
public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) {
if (received.getMessage().equals(expectedMessage)) {
syncPoint.signal();
} else {
syncPoint.signalFailure("Received decrypted message was not equal to sent message.");
@ -156,8 +162,8 @@ public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIn
}
@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
// Ignored.
public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage) {
}
}
}

View file

@ -20,19 +20,8 @@
*/
package org.jivesoftware.smackx.omemo.signal;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.logging.Level;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoService;
import org.jivesoftware.smackx.omemo.OmemoStore;
@ -73,11 +62,7 @@ public final class SignalOmemoService
return new SignalOmemoRatchet(manager, getOmemoStoreBackend());
}
public static void setup()
throws InvalidKeyException, XMPPErrorException, NoSuchPaddingException, InvalidAlgorithmParameterException,
UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
NoSuchProviderException, SmackException, InterruptedException, CorruptedOmemoKeyException
{
public static void setup() {
if (!LICENSE_ACKNOWLEDGED) {
throw new IllegalStateException("smack-omemo-signal is licensed under the terms of the GPLv3. " +
"Please be aware that you can only use this library within the terms of the GPLv3. " +
@ -98,11 +83,7 @@ public final class SignalOmemoService
return new SignalCachingOmemoStore();
}
private SignalOmemoService()
throws SmackException, InterruptedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException,
NoSuchPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException,
IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchProviderException,
java.security.InvalidKeyException {
private SignalOmemoService() {
super();
}

View file

@ -225,18 +225,19 @@ public class SignalOmemoStoreConnector
@Override
public SignedPreKeyRecord loadSignedPreKey(int i) throws InvalidKeyIdException {
return omemoStore.loadOmemoSignedPreKey(getOurDevice(), i);
SignedPreKeyRecord signedPreKeyRecord = omemoStore.loadOmemoSignedPreKey(getOurDevice(), i);
if (signedPreKeyRecord == null) {
throw new InvalidKeyIdException("No signed preKey with id " + i + " found.");
}
return signedPreKeyRecord;
}
@Override
public List<SignedPreKeyRecord> loadSignedPreKeys() {
List<SignedPreKeyRecord> signedPreKeyRecordList = new ArrayList<>();
TreeMap<Integer, SignedPreKeyRecord> signedPreKeyRecordHashMap =
omemoStore.loadOmemoSignedPreKeys(getOurDevice());
signedPreKeyRecordList.addAll(signedPreKeyRecordHashMap.values());
return signedPreKeyRecordList;
return new ArrayList<>(signedPreKeyRecordHashMap.values());
}
@Override
@ -259,7 +260,7 @@ public class SignalOmemoStoreConnector
omemoStore.removeOmemoSignedPreKey(getOurDevice(), i);
}
public static OmemoDevice asOmemoDevice(SignalProtocolAddress address) throws XmppStringprepException {
private static OmemoDevice asOmemoDevice(SignalProtocolAddress address) throws XmppStringprepException {
return new OmemoDevice(JidCreate.bareFrom(address.getName()), address.getDeviceId());
}

View file

@ -20,7 +20,6 @@
*/
package org.jivesoftware.smackx.omemo;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
@ -51,7 +50,7 @@ public class SignalOmemoKeyUtilTest
}
@Parameterized.Parameters
public static Collection<Object[]> getParameters() throws IOException {
public static Collection<Object[]> getParameters() {
return Arrays.asList(new Object[][] {
{ new SignalOmemoKeyUtil()}
});

View file

@ -26,23 +26,11 @@ import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertNotSame;
import static junit.framework.TestCase.assertTrue;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.jivesoftware.smack.DummyConnection;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.provider.OmemoVAxolotlProvider;
import org.jivesoftware.smackx.omemo.signal.SignalOmemoService;
@ -54,12 +42,7 @@ import org.junit.Test;
public class SignalOmemoManagerTest extends SmackTestSuite {
@Test
public void instantiationTest()
throws CorruptedOmemoKeyException, NoSuchAlgorithmException, UnsupportedEncodingException,
InvalidKeyException, InterruptedException, XMPPException.XMPPErrorException, NoSuchPaddingException,
BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException, IllegalBlockSizeException,
SmackException
{
public void instantiationTest() {
SignalOmemoService.acknowledgeLicense();
SignalOmemoService.setup();

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,7 +349,7 @@ 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,
private OmemoMessage.Sent encrypt(OmemoManager.LoggedInOmemoManager managerGuard,
List<OmemoDevice> contactsDevices,
byte[] messageKey,
byte[] iv,
@ -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 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());
}
}