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 @AfterClass
public void cleanUp() throws SmackException.NotLoggedInException { public void cleanUp() {
alice.stopListeners(); alice.stopListeners();
bob.stopListeners(); bob.stopListeners();
OmemoManagerSetupHelper.cleanUpPubSub(alice); OmemoManagerSetupHelper.cleanUpPubSub(alice);

View File

@ -22,9 +22,8 @@ import static org.junit.Assert.assertFalse;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement; 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.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
import org.igniterealtime.smack.inttest.SmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTest;
@ -51,13 +50,13 @@ public class MessageEncryptionIntegrationTest extends AbstractTwoUsersOmemoInteg
OmemoBundleElement bobsBundle1 = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice()); OmemoBundleElement bobsBundle1 = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice());
final String message1 = "One is greater than zero (for small values of zero)."; 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(); final SimpleResultSyncPoint bobReceivedMessage = new SimpleResultSyncPoint();
bob.addOmemoMessageListener(new OmemoMessageListener() { bob.addOmemoMessageListener(new OmemoMessageListener() {
@Override @Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) { public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) {
if (decryptedBody.equals(message1)) { if (received.getMessage().equals(message1)) {
bobReceivedMessage.signal(); bobReceivedMessage.signal();
} else { } else {
bobReceivedMessage.signalFailure("Received decrypted message was not equal to sent message."); bobReceivedMessage.signalFailure("Received decrypted message was not equal to sent message.");
@ -65,13 +64,16 @@ public class MessageEncryptionIntegrationTest extends AbstractTwoUsersOmemoInteg
} }
@Override @Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) { public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage) {
// Not used // Not needed
} }
}); });
encrypted1.setTo(bob.getOwnJid()); Message m1 = new Message();
alice.getConnection().sendStanza(encrypted1); m1.addExtension(encrypted1.getElement());
m1.setTo(bob.getOwnJid());
alice.getConnection().sendStanza(m1);
bobReceivedMessage.waitForResult(10 * 1000); bobReceivedMessage.waitForResult(10 * 1000);
OmemoBundleElement aliceBundle2 = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice()); OmemoBundleElement aliceBundle2 = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice());
@ -82,13 +84,13 @@ public class MessageEncryptionIntegrationTest extends AbstractTwoUsersOmemoInteg
assertFalse(bobsBundle1.equals(bobsBundle2)); assertFalse(bobsBundle1.equals(bobsBundle2));
final String message2 = "The german words for 'leek' and 'wimp' are the same."; 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(); final SimpleResultSyncPoint aliceReceivedMessage = new SimpleResultSyncPoint();
alice.addOmemoMessageListener(new OmemoMessageListener() { alice.addOmemoMessageListener(new OmemoMessageListener() {
@Override @Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) { public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) {
if (decryptedBody.equals(message2)) { if (received.getMessage().equals(message2)) {
aliceReceivedMessage.signal(); aliceReceivedMessage.signal();
} else { } else {
aliceReceivedMessage.signalFailure("Received decrypted message was not equal to sent message."); aliceReceivedMessage.signalFailure("Received decrypted message was not equal to sent message.");
@ -96,13 +98,15 @@ public class MessageEncryptionIntegrationTest extends AbstractTwoUsersOmemoInteg
} }
@Override @Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) { public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage) {
// Not needed here either. // Not needed
} }
}); });
encrypted2.setTo(alice.getOwnJid()); Message m2 = new Message();
bob.getConnection().sendStanza(encrypted2); m2.addExtension(encrypted2.getElement());
m2.setTo(alice.getOwnJid());
bob.getConnection().sendStanza(m2);
aliceReceivedMessage.waitForResult(10 * 1000); aliceReceivedMessage.waitForResult(10 * 1000);
OmemoBundleElement aliceBundle3 = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice()); 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.SmackException;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.omemo.util.EphemeralTrustCallback;
import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.roster.PresenceEventListener; import org.jivesoftware.smack.roster.PresenceEventListener;
import org.jivesoftware.smack.roster.Roster; 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.OmemoCachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice; import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint; 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.omemo.util.OmemoConstants;
import org.jivesoftware.smackx.pubsub.PubSubException; import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.PubSubManager; import org.jivesoftware.smackx.pubsub.PubSubManager;
@ -45,6 +45,7 @@ import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.FullJid; import org.jxmpp.jid.FullJid;
import org.jxmpp.jid.Jid; import org.jxmpp.jid.Jid;
public class OmemoManagerSetupHelper { public class OmemoManagerSetupHelper {
/** /**
@ -177,14 +178,11 @@ public class OmemoManagerSetupHelper {
} }
} }
public static void cleanUpPubSub(OmemoManager omemoManager) public static void cleanUpPubSub(OmemoManager omemoManager) {
throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
PubSubException.NotALeafNodeException
{
PubSubManager pm = PubSubManager.getInstance(omemoManager.getConnection(),omemoManager.getOwnJid()); PubSubManager pm = PubSubManager.getInstance(omemoManager.getConnection(),omemoManager.getOwnJid());
try { try {
omemoManager.requestDeviceListUpdateFor(omemoManager.getOwnJid()); omemoManager.requestDeviceListUpdateFor(omemoManager.getOwnJid());
} catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException e) { } catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException e) {
// ignore // ignore
} }

View File

@ -23,8 +23,7 @@ import java.util.concurrent.TimeoutException;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener; import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
import org.igniterealtime.smack.inttest.SmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTest;
@ -41,16 +40,16 @@ public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIn
super(environment); 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 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 ResultSyncPoint<Boolean, IllegalStateException> bsp2 = new ResultSyncPoint<>();
private final OmemoMessageListener bml2 = new OmemoMessageListener() { private final OmemoMessageListener bml2 = new OmemoMessageListener() {
@Override @Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) { public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) {
if (decryptedBody.equals(m2)) { if (received.getMessage().equals(body2)) {
bsp2.signal(new IllegalStateException("Message MUST NOT be decryptable!")); bsp2.signal(new IllegalStateException("Message MUST NOT be decryptable!"));
} else { } else {
bsp2.signal(new IllegalStateException("OmemoMessageListener MUST NOT be called for this message.")); bsp2.signal(new IllegalStateException("OmemoMessageListener MUST NOT be called for this message."));
@ -58,37 +57,40 @@ public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIn
} }
@Override @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 SimpleResultSyncPoint asp2 = new SimpleResultSyncPoint();
private final OmemoMessageListener aml2 = new OmemoMessageListener() { 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 @Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) { public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received received) {
asp2.signal(); 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 SimpleResultSyncPoint bsp3 = new SimpleResultSyncPoint();
private final OmemoMessageListener bml3 = new OmemoTestMessageListener(m3, bsp3); private final OmemoMessageListener bml3 = new OmemoTestMessageListener(body3, bsp3);
@SmackIntegrationTest @SmackIntegrationTest
public void sessionRenegotiationTest() throws Exception { public void sessionRenegotiationTest() throws Exception {
/* /*
Send (PreKey-)message from Alice to Bob to initiate a session. Send (PreKey-)message from Alice to Bob to initiate a session.
*/ */
Message e1 = alice.encrypt(bob.getOwnJid(), m1); OmemoMessage.Sent e1 = alice.encrypt(bob.getOwnJid(), body1);
e1.setTo(bob.getOwnJid()); Message m1 = new Message();
m1.addExtension(e1.getElement());
m1.setTo(bob.getOwnJid());
bob.addOmemoMessageListener(bml1); bob.addOmemoMessageListener(bml1);
alice.getConnection().sendStanza(e1); alice.getConnection().sendStanza(m1);
bsp1.waitForResult(10 * 1000); bsp1.waitForResult(10 * 1000);
bob.removeOmemoMessageListener(bml1); 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). Send normal message from Alice to Bob (Alice assumes, that Bob still has a valid session).
*/ */
Message e2 = alice.encrypt(bob.getOwnJid(), m2); OmemoMessage.Sent e2 = alice.encrypt(bob.getOwnJid(), body2);
e2.setTo(bob.getOwnJid()); Message m2 = new Message();
m2.addExtension(e2.getElement());
m2.setTo(bob.getOwnJid());
bob.addOmemoMessageListener(bml2); bob.addOmemoMessageListener(bml2);
alice.addOmemoMessageListener(aml2); alice.addOmemoMessageListener(aml2);
alice.getConnection().sendStanza(e2); alice.getConnection().sendStanza(m2);
/* /*
Wait for the timeout on Bobs side, since message decryption will fail now. 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 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. which Bob can decrypt successfully again.
*/ */
Message e3 = alice.encrypt(bob.getOwnJid(), m3); OmemoMessage.Sent e3 = alice.encrypt(bob.getOwnJid(), body3);
e3.setTo(bob.getOwnJid()); Message m3 = new Message();
m3.addExtension(e3.getElement());
m3.setTo(bob.getOwnJid());
bob.addOmemoMessageListener(bml3); bob.addOmemoMessageListener(bml3);
alice.getConnection().sendStanza(e3); alice.getConnection().sendStanza(m3);
bsp3.waitForResult(10 * 1000); bsp3.waitForResult(10 * 1000);
bob.removeOmemoMessageListener(bml3); bob.removeOmemoMessageListener(bml3);
} }
@ -147,8 +153,8 @@ public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIn
} }
@Override @Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) { public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) {
if (decryptedBody.equals(expectedMessage)) { if (received.getMessage().equals(expectedMessage)) {
syncPoint.signal(); syncPoint.signal();
} else { } else {
syncPoint.signalFailure("Received decrypted message was not equal to sent message."); syncPoint.signalFailure("Received decrypted message was not equal to sent message.");
@ -156,8 +162,8 @@ public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIn
} }
@Override @Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) { public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage) {
// Ignored.
} }
} }
} }

View File

@ -20,19 +20,8 @@
*/ */
package org.jivesoftware.smackx.omemo.signal; 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 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.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoService; import org.jivesoftware.smackx.omemo.OmemoService;
import org.jivesoftware.smackx.omemo.OmemoStore; import org.jivesoftware.smackx.omemo.OmemoStore;
@ -73,11 +62,7 @@ public final class SignalOmemoService
return new SignalOmemoRatchet(manager, getOmemoStoreBackend()); return new SignalOmemoRatchet(manager, getOmemoStoreBackend());
} }
public static void setup() public static void setup() {
throws InvalidKeyException, XMPPErrorException, NoSuchPaddingException, InvalidAlgorithmParameterException,
UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
NoSuchProviderException, SmackException, InterruptedException, CorruptedOmemoKeyException
{
if (!LICENSE_ACKNOWLEDGED) { if (!LICENSE_ACKNOWLEDGED) {
throw new IllegalStateException("smack-omemo-signal is licensed under the terms of the GPLv3. " + 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. " + "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(); return new SignalCachingOmemoStore();
} }
private SignalOmemoService() private SignalOmemoService() {
throws SmackException, InterruptedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException,
NoSuchPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException,
IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchProviderException,
java.security.InvalidKeyException {
super(); super();
} }

View File

@ -225,18 +225,19 @@ public class SignalOmemoStoreConnector
@Override @Override
public SignedPreKeyRecord loadSignedPreKey(int i) throws InvalidKeyIdException { 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 @Override
public List<SignedPreKeyRecord> loadSignedPreKeys() { public List<SignedPreKeyRecord> loadSignedPreKeys() {
List<SignedPreKeyRecord> signedPreKeyRecordList = new ArrayList<>();
TreeMap<Integer, SignedPreKeyRecord> signedPreKeyRecordHashMap = TreeMap<Integer, SignedPreKeyRecord> signedPreKeyRecordHashMap =
omemoStore.loadOmemoSignedPreKeys(getOurDevice()); omemoStore.loadOmemoSignedPreKeys(getOurDevice());
signedPreKeyRecordList.addAll(signedPreKeyRecordHashMap.values()); return new ArrayList<>(signedPreKeyRecordHashMap.values());
return signedPreKeyRecordList;
} }
@Override @Override
@ -259,7 +260,7 @@ public class SignalOmemoStoreConnector
omemoStore.removeOmemoSignedPreKey(getOurDevice(), i); 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()); return new OmemoDevice(JidCreate.bareFrom(address.getName()), address.getDeviceId());
} }

View File

@ -20,7 +20,6 @@
*/ */
package org.jivesoftware.smackx.omemo; package org.jivesoftware.smackx.omemo;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -51,7 +50,7 @@ public class SignalOmemoKeyUtilTest
} }
@Parameterized.Parameters @Parameterized.Parameters
public static Collection<Object[]> getParameters() throws IOException { public static Collection<Object[]> getParameters() {
return Arrays.asList(new Object[][] { return Arrays.asList(new Object[][] {
{ new SignalOmemoKeyUtil()} { 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.assertNotSame;
import static junit.framework.TestCase.assertTrue; 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.DummyConnection;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.test.util.SmackTestSuite; import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smack.test.util.TestUtils; import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smackx.omemo.element.OmemoElement; 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.provider.OmemoVAxolotlProvider;
import org.jivesoftware.smackx.omemo.signal.SignalOmemoService; import org.jivesoftware.smackx.omemo.signal.SignalOmemoService;
@ -54,12 +42,7 @@ import org.junit.Test;
public class SignalOmemoManagerTest extends SmackTestSuite { public class SignalOmemoManagerTest extends SmackTestSuite {
@Test @Test
public void instantiationTest() public void instantiationTest() {
throws CorruptedOmemoKeyException, NoSuchAlgorithmException, UnsupportedEncodingException,
InvalidKeyException, InterruptedException, XMPPException.XMPPErrorException, NoSuchPaddingException,
BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException, IllegalBlockSizeException,
SmackException
{
SignalOmemoService.acknowledgeLicense(); SignalOmemoService.acknowledgeLicense();
SignalOmemoService.setup(); SignalOmemoService.setup();

View File

@ -29,6 +29,7 @@ import java.util.Random;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractConnectionListener; import org.jivesoftware.smack.AbstractConnectionListener;
@ -38,6 +39,7 @@ import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.NamedElement; import org.jivesoftware.smack.packet.NamedElement;
import org.jivesoftware.smack.packet.Stanza; 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.MultiUserChatManager;
import org.jivesoftware.smackx.muc.RoomInfo; import org.jivesoftware.smackx.muc.RoomInfo;
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement; 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;
import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException; import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException; import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
import org.jivesoftware.smackx.omemo.exceptions.NoOmemoSupportException; import org.jivesoftware.smackx.omemo.exceptions.NoOmemoSupportException;
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException; 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.OmemoCachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice; 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.OmemoMessageListener;
import org.jivesoftware.smackx.omemo.listener.OmemoMucMessageListener; import org.jivesoftware.smackx.omemo.listener.OmemoMucMessageListener;
import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint; import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.trust.TrustCallback; import org.jivesoftware.smackx.omemo.trust.TrustCallback;
import org.jivesoftware.smackx.omemo.trust.TrustState; import org.jivesoftware.smackx.omemo.trust.TrustState;
import org.jivesoftware.smackx.pep.PEPListener;
import org.jivesoftware.smackx.pep.PEPManager; 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.PubSubException;
import org.jivesoftware.smackx.pubsub.packet.PubSub; 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. * @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() { Async.go(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -287,8 +293,8 @@ public final class OmemoManager extends Manager {
* @throws SmackException.NoResponseException * @throws SmackException.NoResponseException
*/ */
public OmemoMessage.Sent encrypt(BareJid recipient, String message) public OmemoMessage.Sent encrypt(BareJid recipient, String message)
throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, throws CryptoFailedException, UndecidedOmemoIdentityException,
InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException, InterruptedException, SmackException.NotConnectedException,
SmackException.NoResponseException, SmackException.NotLoggedInException SmackException.NoResponseException, SmackException.NotLoggedInException
{ {
synchronized (LOCK) { synchronized (LOCK) {
@ -314,8 +320,8 @@ public final class OmemoManager extends Manager {
* @throws SmackException.NoResponseException * @throws SmackException.NoResponseException
*/ */
public OmemoMessage.Sent encrypt(ArrayList<BareJid> recipients, String message) public OmemoMessage.Sent encrypt(ArrayList<BareJid> recipients, String message)
throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, throws CryptoFailedException, UndecidedOmemoIdentityException,
InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException, InterruptedException, SmackException.NotConnectedException,
SmackException.NoResponseException, SmackException.NotLoggedInException SmackException.NoResponseException, SmackException.NotLoggedInException
{ {
synchronized (LOCK) { synchronized (LOCK) {
@ -346,9 +352,9 @@ public final class OmemoManager extends Manager {
* with any of their devices. * with any of their devices.
*/ */
public OmemoMessage.Sent encrypt(MultiUserChat muc, String message) public OmemoMessage.Sent encrypt(MultiUserChat muc, String message)
throws UndecidedOmemoIdentityException, NoSuchAlgorithmException, CryptoFailedException, throws UndecidedOmemoIdentityException, CryptoFailedException,
XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, NoOmemoSupportException, CannotEstablishOmemoSessionException, SmackException.NoResponseException, NoOmemoSupportException,
SmackException.NotLoggedInException SmackException.NotLoggedInException
{ {
synchronized (LOCK) { 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) { void setDeviceId(int nDeviceId) {
synchronized (LOCK) { synchronized (LOCK) {
// Move this instance inside the HashMaps // Move this instance inside the HashMaps
@ -735,64 +745,58 @@ public final class OmemoManager extends Manager {
/** /**
* Notify all registered OmemoMessageListeners about a received OmemoMessage. * Notify all registered OmemoMessageListeners about a received OmemoMessage.
* *
* @param decryptedBody decrypted Body element of the message * @param stanza original stanza
* @param encryptedMessage unmodified message as it was received * @param decryptedMessage decrypted OmemoMessage.
* @param wrappingMessage message that wrapped the incoming message
* @param messageInformation information about the messages encryption (used identityKey, carbon...)
*/ */
void notifyOmemoMessageReceived(String decryptedBody, void notifyOmemoMessageReceived(Stanza stanza, OmemoMessage.Received decryptedMessage)
Message encryptedMessage,
Message wrappingMessage,
OmemoMessageInformation messageInformation)
{ {
for (OmemoMessageListener l : omemoMessageListeners) { for (OmemoMessageListener l : omemoMessageListeners) {
l.onOmemoMessageReceived(decryptedBody, encryptedMessage, wrappingMessage, messageInformation); l.onOmemoMessageReceived(stanza, decryptedMessage);
} }
} }
void notifyOmemoKeyTransportMessageReceived(CipherAndAuthTag cipherAndAuthTag, /**
Message transportingMessage, * Notify all registered OmemoMessageListeners about a received OMEMO KeyTransportMessage.
Message wrappingMessage, *
OmemoMessageInformation information) * @param stanza original stanza.
* @param decryptedKeyTransportMessage decrypted KeyTransportMessage.
*/
void notifyOmemoKeyTransportMessageReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage)
{ {
for (OmemoMessageListener l : omemoMessageListeners) { 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. * Notify all registered OmemoMucMessageListeners of an incoming OmemoMessageElement in a MUC.
* *
* @param muc MultiUserChat the message was received in * @param muc MultiUserChat the message was received in.
* @param from BareJid of the user that sent the message * @param stanza Original Stanza.
* @param decryptedBody decrypted body * @param decryptedMessage Decryped OmemoMessage.
* @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
*/ */
void notifyOmemoMucMessageReceived(MultiUserChat muc, void notifyOmemoMucMessageReceived(MultiUserChat muc,
BareJid from, Stanza stanza,
String decryptedBody, OmemoMessage.Received decryptedMessage)
Message message,
Message wrappingMessage,
OmemoMessageInformation omemoInformation)
{ {
for (OmemoMucMessageListener l : omemoMucMessageListeners) { for (OmemoMucMessageListener l : omemoMucMessageListeners) {
l.onOmemoMucMessageReceived(muc, from, decryptedBody, message, l.onOmemoMucMessageReceived(muc, stanza, decryptedMessage);
wrappingMessage, omemoInformation);
} }
} }
/**
* 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, void notifyOmemoMucKeyTransportMessageReceived(MultiUserChat muc,
BareJid from, Stanza stanza,
CipherAndAuthTag cipherAndAuthTag, OmemoMessage.Received decryptedKeyTransportMessage)
Message transportingMessage,
Message wrappingMessage,
OmemoMessageInformation messageInformation)
{ {
for (OmemoMucMessageListener l : omemoMucMessageListeners) { for (OmemoMucMessageListener l : omemoMucMessageListeners) {
l.onOmemoKeyTransportReceived(muc, from, cipherAndAuthTag, l.onOmemoKeyTransportReceived(muc, stanza, decryptedKeyTransportMessage);
transportingMessage, wrappingMessage, messageInformation);
} }
} }
@ -808,10 +812,10 @@ public final class OmemoManager extends Manager {
// Remove listeners to avoid them getting added twice // Remove listeners to avoid them getting added twice
connection().removeAsyncStanzaListener(internalOmemoMessageStanzaListener); connection().removeAsyncStanzaListener(internalOmemoMessageStanzaListener);
carbonManager.removeCarbonCopyReceivedListener(internalOmemoCarbonCopyListener); carbonManager.removeCarbonCopyReceivedListener(internalOmemoCarbonCopyListener);
//pepManager.removePEPListener(deviceListUpdateListener); pepManager.removePEPListener(deviceListUpdateListener);
// Add listeners // Add listeners
//pepManager.addPEPListener(deviceListUpdateListener); pepManager.addPEPListener(deviceListUpdateListener);
connection().addAsyncStanzaListener(internalOmemoMessageStanzaListener, omemoMessageStanzaFilter); connection().addAsyncStanzaListener(internalOmemoMessageStanzaListener, omemoMessageStanzaFilter);
carbonManager.addCarbonCopyReceivedListener(internalOmemoCarbonCopyListener); carbonManager.addCarbonCopyReceivedListener(internalOmemoCarbonCopyListener);
} }
@ -820,7 +824,7 @@ public final class OmemoManager extends Manager {
* Remove active stanza listeners needed for OMEMO. * Remove active stanza listeners needed for OMEMO.
*/ */
public void stopListeners() { public void stopListeners() {
//PEPManager.getInstanceFor(connection()).removePEPListener(deviceListUpdateListener); PEPManager.getInstanceFor(connection()).removePEPListener(deviceListUpdateListener);
connection().removeAsyncStanzaListener(internalOmemoMessageStanzaListener); connection().removeAsyncStanzaListener(internalOmemoMessageStanzaListener);
CarbonManager.getInstanceFor(connection()).removeCarbonCopyReceivedListener(internalOmemoCarbonCopyListener); CarbonManager.getInstanceFor(connection()).removeCarbonCopyReceivedListener(internalOmemoCarbonCopyListener);
} }
@ -844,6 +848,9 @@ public final class OmemoManager extends Manager {
return service; return service;
} }
/**
* StanzaListener that listens for incoming Stanzas which contain OMEMO elements.
*/
private final StanzaListener internalOmemoMessageStanzaListener = new StanzaListener() { private final StanzaListener internalOmemoMessageStanzaListener = new StanzaListener() {
@Override @Override
public void processStanza(Stanza packet) throws SmackException.NotConnectedException, InterruptedException { 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() { private final CarbonCopyReceivedListener internalOmemoCarbonCopyListener = new CarbonCopyReceivedListener() {
@Override @Override
public void onCarbonCopyReceived(CarbonExtension.Direction direction, 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. * 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 { public static class LoggedInOmemoManager {
private final OmemoManager manager; 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 initializationFinished(OmemoManager manager);
void initializationFailed(Exception cause); 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) { private static void initBareJidAndDeviceId(OmemoManager manager) {
if (!manager.getConnection().isAuthenticated()) { if (!manager.getConnection().isAuthenticated()) {
throw new IllegalStateException("Connection MUST be authenticated."); throw new IllegalStateException("Connection MUST be authenticated.");

View File

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

View File

@ -24,7 +24,6 @@ import java.util.logging.Logger;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.omemo.element.OmemoElement; import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.element.OmemoKeyElement; 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 OmemoManager omemoManager;
protected final OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store; 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, public OmemoRatchet(OmemoManager omemoManager,
OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store) { OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store) {
this.omemoManager = omemoManager; this.omemoManager = omemoManager;
this.store = store; 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) public abstract byte[] doubleRatchetDecrypt(OmemoDevice sender, byte[] encryptedKey)
throws CorruptedOmemoKeyException, NoRawSessionException, CryptoFailedException, throws CorruptedOmemoKeyException, NoRawSessionException, CryptoFailedException,
UntrustedOmemoIdentityException; 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); 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<CryptoFailedException> decryptExceptions = new ArrayList<>();
List<OmemoKeyElement> keys = element.getHeader().getKeys(); List<OmemoKeyElement> keys = element.getHeader().getKeys();
boolean preKey = false;
// Find key with our ID. // Find key with our ID.
for (OmemoKeyElement k : keys) { for (OmemoKeyElement k : keys) {
if (k.getId() == keyId) { if (k.getId() == keyId) {
try { try {
unpackedKey = doubleRatchetDecrypt(sender, k.getData()); unpackedKey = doubleRatchetDecrypt(sender, k.getData());
preKey = k.isPreKey();
break; break;
} catch (CryptoFailedException e) { } catch (CryptoFailedException e) {
// There might be multiple keys with our id, but we can only decrypt one. // 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."); + 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 element omemoElement containing a payload.
* @param cipherAndAuthTag cipher and authentication tag. * @param cipherAndAuthTag cipher and authentication tag.
* @return Message containing the decrypted payload in its body. * @return decrypted plain text.
* @throws CryptoFailedException * @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()) { if (!element.isMessageElement()) {
throw new IllegalArgumentException("decryptMessageElement cannot decrypt OmemoElement which is no MessageElement!"); 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 { try {
String plaintext = new String(cipherAndAuthTag.getCipher().doFinal(encryptedBody), StringUtils.UTF8); String plaintext = new String(cipherAndAuthTag.getCipher().doFinal(encryptedBody), StringUtils.UTF8);
Message decrypted = new Message(); return plaintext;
decrypted.setBody(plaintext);
return decrypted;
} catch (UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException e) { } catch (UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException e) {
throw new CryptoFailedException("decryptMessageElement could not decipher message body: " 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.HashMap;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
@ -45,17 +46,23 @@ import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.carbons.packet.CarbonExtension; 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.OmemoBundleElement;
import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement; import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement;
import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement_VAxolotl; import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement_VAxolotl;
import org.jivesoftware.smackx.omemo.element.OmemoElement; 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.CannotEstablishOmemoSessionException;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException; import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
import org.jivesoftware.smackx.omemo.exceptions.NoIdentityKeyException; 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.StaleDeviceException;
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException; import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException; 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.OmemoCachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice; import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.listener.OmemoCarbonCopyStanzaReceivedListener; 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.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jxmpp.jid.BareJid; 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. * 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; 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; private 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 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. * 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 BadPaddingException when cipher.doFinal gets wrong padding
* @throws IllegalBlockSizeException when cipher.doFinal gets wrong Block size. * @throws IllegalBlockSizeException when cipher.doFinal gets wrong Block size.
*/ */
protected OmemoService() protected OmemoService() {
throws NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, IllegalBlockSizeException,
BadPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException
{
// TODO // TODO
} }
@ -178,6 +184,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* *
* @param omemoStore store. * @param omemoStore store.
*/ */
@SuppressWarnings("unused")
public void setOmemoStoreBackend( public void setOmemoStoreBackend(
OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore) { OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore) {
if (this.omemoStore != null) { 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 UndecidedOmemoIdentityException if the list of recipient devices contains undecided devices
* @throws CryptoFailedException if we are lacking some crypto primitives * @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, List<OmemoDevice> contactsDevices,
byte[] messageKey, byte[] messageKey,
byte[] iv, byte[] iv,
String message) String message)
throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException,
UndecidedOmemoIdentityException, CryptoFailedException UndecidedOmemoIdentityException, CryptoFailedException
{ {
@ -418,23 +425,72 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
OmemoElement element = builder.finish(); 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 InterruptedException
* @throws UndecidedOmemoIdentityException if the list of recipients contains an undecided device * @throws UndecidedOmemoIdentityException if the list of recipients contains an undecided device
* @throws CryptoFailedException if we are lacking some cryptographic algorithms * @throws CryptoFailedException if we are lacking some cryptographic algorithms
* @throws SmackException.NotConnectedException * @throws SmackException.NotConnectedException
* @throws SmackException.NoResponseException * @throws SmackException.NoResponseException
*/ */
OmemoMessage.Sent createKeyTransportMessage(OmemoManager.LoggedInOmemoManager managerGuard, OmemoMessage.Sent createKeyTransportElement(OmemoManager.LoggedInOmemoManager managerGuard,
List<OmemoDevice> contactsDevices, List<OmemoDevice> contactsDevices,
byte[] key, byte[] key,
byte[] iv) byte[] iv)
@ -570,14 +626,24 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @throws SmackException.NoResponseException * @throws SmackException.NoResponseException
* @throws PubSubException.NotALeafNodeException * @throws PubSubException.NotALeafNodeException
*/ */
private static void publishDeviceList(XMPPConnection connection, OmemoDeviceListElement deviceList) static void publishDeviceList(XMPPConnection connection, OmemoDeviceListElement deviceList)
throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
SmackException.NoResponseException, PubSubException.NotALeafNodeException SmackException.NoResponseException
{ {
PubSubManager.getInstance(connection, connection.getUser().asBareJid()) PubSubManager.getInstance(connection, connection.getUser().asBareJid())
.tryToPublishAndPossibleAutoCreate(OmemoConstants.PEP_NODE_DEVICE_LIST, new PayloadItem<>(deviceList)); .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) private void refreshAndRepublishDeviceList(XMPPConnection connection, OmemoDevice userDevice)
throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException SmackException.NotConnectedException, SmackException.NoResponseException
@ -592,21 +658,10 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
if (publishedList == null) { if (publishedList == null) {
publishedList = new OmemoDeviceListElement_VAxolotl(Collections.<Integer>emptySet()); 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 OmemoCachedDeviceList cachedList = cleanUpDeviceList(userDevice);
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);
// Republish our deviceId if it is missing from the published list. // Republish our deviceId if it is missing from the published list.
if (!publishedList.getDeviceIds().equals(cachedList.getActiveDevices())) { 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. * 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. * @param contactsDevice OmemoDevice of the contact.
* @return true if userDevice has session with contactsDevice. * @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; 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 userDevice our OmemoDevice
* @param devices collection of OmemoDevices * @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)) { if (devices.contains(userDevice)) {
devices.remove(userDevice); devices.remove(userDevice);
} }
@ -996,7 +1078,74 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
} }
@Override @Override
public void onOmemoMessageStanzaReceived(Stanza stanza, OmemoManager.LoggedInOmemoManager omemoManager) { public void onOmemoMessageStanzaReceived(Stanza stanza, OmemoManager.LoggedInOmemoManager managerGuard) {
// TODO: Implement 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 class OmemoElement_VAxolotl extends OmemoElement {
public static final String NAMESPACE = OMEMO_NAMESPACE_V_AXOLOTL;
/** /**
* Create a new OmemoMessageElement from a header and a payload. * Create a new OmemoMessageElement from a header and a payload.
* *
@ -37,6 +39,6 @@ public class OmemoElement_VAxolotl extends OmemoElement {
@Override @Override
public String getNamespace() { 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; package org.jivesoftware.smackx.omemo.element;
import org.jivesoftware.smack.packet.NamedElement; import org.jivesoftware.smack.packet.NamedElement;

View File

@ -78,4 +78,16 @@ public class UntrustedOmemoIdentityException extends Exception {
public OmemoFingerprint getUntrustedFingerprint() { public OmemoFingerprint getUntrustedFingerprint() {
return untrustedKey; 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.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException; import java.security.NoSuchProviderException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
@ -38,11 +37,13 @@ import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
*/ */
public class CipherAndAuthTag { public class CipherAndAuthTag {
private final byte[] key, iv, authTag; 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.authTag = authTag;
this.key = key; this.key = key;
this.iv = iv; this.iv = iv;
this.wasPreKey = wasPreKey;
} }
public Cipher getCipher() throws CryptoFailedException { public Cipher getCipher() throws CryptoFailedException {
@ -82,4 +83,8 @@ public class CipherAndAuthTag {
} }
return null; return null;
} }
public boolean wasPreKeyEncrypted() {
return wasPreKey;
}
} }

View File

@ -16,10 +16,8 @@
*/ */
package org.jivesoftware.smackx.omemo.listener; package org.jivesoftware.smackx.omemo.listener;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.omemo.OmemoMessage;
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
/** /**
* Listener interface that allows implementations to receive decrypted OMEMO messages. * 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. * Gets called, whenever an OmemoMessage has been received and was successfully decrypted.
* *
* @param decryptedBody Decrypted body * @param stanza Received (encrypted) stanza.
* @param encryptedMessage Encrypted Message * @param decryptedMessage decrypted OmemoMessage.
* @param wrappingMessage Wrapping carbon message, in case the message was a carbon copy, else null.
* @param omemoInformation Information about the messages encryption etc.
*/ */
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. * Gets called, whenever an OmemoElement without a body (an OmemoKeyTransportElement) is received.
* *
* @param cipherAndAuthTag transported Cipher along with an optional AuthTag * @param stanza received (encrypted) stanza.
* @param message Message that contained the KeyTransport * @param decryptedKeyTransportMessage decrypted OMEMO key transport message.
* @param wrappingMessage Wrapping message (eg. carbon), or null
* @param omemoInformation Information about the messages encryption etc.
*/ */
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; 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.muc.MultiUserChat;
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag; import org.jivesoftware.smackx.omemo.OmemoMessage;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.jxmpp.jid.BareJid;
/** /**
* Listener interface that allows implementations to receive decrypted OMEMO MUC messages. * 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. * Gets called whenever an OMEMO message has been received in a MultiUserChat and successfully decrypted.
* @param muc MultiUserChat the message was sent in * @param muc MultiUserChat the message was sent in
* @param from the bareJid of the sender * @param stanza Original Stanza
* @param decryptedBody the decrypted Body of the message * @param decryptedOmemoMessage decrypted Omemo 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
*/ */
void onOmemoMucMessageReceived(MultiUserChat muc, BareJid from, String decryptedBody, Message message, void onOmemoMucMessageReceived(MultiUserChat muc, Stanza stanza, OmemoMessage.Received decryptedOmemoMessage);
Message wrappingMessage, OmemoMessageInformation omemoInformation);
/** /**
* Gets called, whenever an OmemoElement without a body (an OmemoKeyTransportElement) is received. * Gets called, whenever an OmemoElement without a body (an OmemoKeyTransportElement) is received.
* *
* @param muc MultiUserChat the message was sent in * @param muc MultiUserChat the message was sent in
* @param from bareJid of the sender * @param stanza original stanza
* @param cipherAndAuthTag transportedKey along with an optional authTag * @param decryptedKeyTransportElement decrypted KeyTransportMessage
* @param message Message that contained the KeyTransport
* @param wrappingMessage Wrapping message (eg. carbon), or null
* @param omemoInformation Information about the messages encryption etc.
*/ */
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 java.io.File;
import org.jivesoftware.smackx.omemo.OmemoConfiguration;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.junit.Test; import org.junit.Test;

View File

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