use of org.jivesoftware.smackx.omemo.element.OmemoBundleElement in project Smack by igniterealtime.
the class MessageEncryptionIntegrationTest method messageTest.
/**
* This test checks whether the following actions are performed.
*
* Alice publishes bundle A1
* Bob publishes bundle B1
*
* Alice sends message to Bob (preKeyMessage)
* Bob publishes bundle B2
* Alice still has A1
*
* Bob responds to Alice (normal message)
* Alice still has A1
* Bob still has B2
* @throws Exception if an exception occurs.
*/
@SuppressWarnings("SynchronizeOnNonFinalField")
@SmackIntegrationTest
public void messageTest() throws Exception {
OmemoBundleElement a1 = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice());
OmemoBundleElement b1 = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice());
// Alice sends message(s) to bob
// PreKeyMessage A -> B
final String body1 = "One is greater than zero (for small values of zero).";
AbstractOmemoMessageListener.PreKeyMessageListener listener1 = new AbstractOmemoMessageListener.PreKeyMessageListener(body1);
bob.addOmemoMessageListener(listener1);
OmemoMessage.Sent e1 = alice.encrypt(bob.getOwnJid(), body1);
XMPPConnection alicesConnection = alice.getConnection();
MessageBuilder messageBuilder = alicesConnection.getStanzaFactory().buildMessageStanza();
alicesConnection.sendStanza(e1.buildMessage(messageBuilder, bob.getOwnJid()));
listener1.getSyncPoint().waitForResult(10 * 1000);
bob.removeOmemoMessageListener(listener1);
OmemoBundleElement a1_ = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice());
OmemoBundleElement b2;
synchronized (bob) {
// Circumvent race condition where bundle gets replenished after getting stored in b2
b2 = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice());
}
assertEquals(a1, a1_, "Alice sent bob a preKeyMessage, so her bundle MUST still be the same.");
assertNotEquals(b1, b2, "Bob just received a preKeyMessage from alice, so his bundle must have changed.");
// Message B -> A
final String body3 = "The german words for 'leek' and 'wimp' are the same.";
AbstractOmemoMessageListener.MessageListener listener3 = new AbstractOmemoMessageListener.MessageListener(body3);
alice.addOmemoMessageListener(listener3);
OmemoMessage.Sent e3 = bob.encrypt(alice.getOwnJid(), body3);
XMPPConnection bobsConnection = bob.getConnection();
messageBuilder = bobsConnection.getStanzaFactory().buildMessageStanza();
bobsConnection.sendStanza(e3.buildMessage(messageBuilder, alice.getOwnJid()));
listener3.getSyncPoint().waitForResult(10 * 1000);
alice.removeOmemoMessageListener(listener3);
OmemoBundleElement a1__ = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice());
OmemoBundleElement b2_ = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice());
assertEquals(a1_, a1__, "Since alice initiated the session with bob, at no time he sent a preKeyMessage, " + "so her bundle MUST still be the same.");
assertEquals(b2, b2_, "Bob changed his bundle earlier, but at this point his bundle must be equal to " + "after the first change.");
}
use of org.jivesoftware.smackx.omemo.element.OmemoBundleElement in project Smack by igniterealtime.
the class OmemoService method onOmemoMessageStanzaReceived.
@Override
public void onOmemoMessageStanzaReceived(Stanza stanza, OmemoManager.LoggedInOmemoManager managerGuard) throws IOException {
OmemoManager manager = managerGuard.get();
// Avoid the ratchet being manipulated and the bundle being published multiple times simultaneously
synchronized (manager) {
OmemoDevice userDevice = manager.getOwnDevice();
OmemoElement element = (OmemoElement) stanza.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoElement_VAxolotl.NAMESPACE);
if (element == null) {
return;
}
OmemoMessage.Received decrypted;
BareJid sender;
try {
MultiUserChat muc = getMuc(manager.getConnection(), stanza.getFrom());
if (muc != null) {
Occupant occupant = muc.getOccupant(stanza.getFrom().asEntityFullJidIfPossible());
if (occupant == null) {
LOGGER.log(Level.WARNING, "Cannot decrypt OMEMO MUC message; MUC Occupant is null.");
return;
}
Jid occupantJid = occupant.getJid();
if (occupantJid == null) {
LOGGER.log(Level.WARNING, "Cannot decrypt OMEMO MUC message; Senders Jid is null. " + stanza.getFrom());
return;
}
sender = occupantJid.asBareJid();
// try is for this
decrypted = decryptMessage(managerGuard, sender, element);
manager.notifyOmemoMucMessageReceived(muc, stanza, decrypted);
} else {
sender = stanza.getFrom().asBareJid();
// and this
decrypted = decryptMessage(managerGuard, sender, element);
manager.notifyOmemoMessageReceived(stanza, decrypted);
}
if (decrypted.isPreKeyMessage() && OmemoConfiguration.getCompleteSessionWithEmptyMessage()) {
LOGGER.log(Level.FINE, "Received a preKeyMessage from " + decrypted.getSenderDevice() + ".\n" + "Complete the session by sending an empty response message.");
try {
sendRatchetUpdate(managerGuard, decrypted.getSenderDevice());
} catch (CannotEstablishOmemoSessionException e) {
throw new AssertionError("Since we successfully received a message, we MUST be able to " + "establish a session. " + e);
} catch (NoSuchAlgorithmException | InterruptedException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
LOGGER.log(Level.WARNING, "Cannot send a ratchet update message.", e);
}
}
} catch (NoRawSessionException e) {
OmemoDevice device = e.getDeviceWithoutSession();
LOGGER.log(Level.WARNING, "No raw session found for contact " + device + ". ", e);
if (OmemoConfiguration.getRepairBrokenSessionsWithPreKeyMessages()) {
repairBrokenSessionWithPreKeyMessage(managerGuard, device);
}
} catch (CorruptedOmemoKeyException | CryptoFailedException e) {
LOGGER.log(Level.WARNING, "Could not decrypt incoming message: ", e);
}
// Upload fresh bundle.
if (getOmemoStoreBackend().loadOmemoPreKeys(userDevice).size() < OmemoConstants.PRE_KEY_COUNT_PER_BUNDLE) {
LOGGER.log(Level.FINE, "We used up a preKey. Upload a fresh bundle.");
try {
getOmemoStoreBackend().replenishKeys(userDevice);
OmemoBundleElement bundleElement = getOmemoStoreBackend().packOmemoBundle(userDevice);
publishBundle(manager.getConnection(), userDevice, bundleElement);
} catch (CorruptedOmemoKeyException | InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | NotALeafNodeException e) {
LOGGER.log(Level.WARNING, "Could not republish replenished bundle.", e);
}
}
}
}
use of org.jivesoftware.smackx.omemo.element.OmemoBundleElement in project Smack by igniterealtime.
the class OmemoService method onOmemoCarbonCopyReceived.
@Override
public void onOmemoCarbonCopyReceived(CarbonExtension.Direction direction, Message carbonCopy, Message wrappingMessage, OmemoManager.LoggedInOmemoManager managerGuard) throws IOException {
OmemoManager manager = managerGuard.get();
// Avoid the ratchet being manipulated and the bundle being published multiple times simultaneously
synchronized (manager) {
OmemoDevice userDevice = manager.getOwnDevice();
OmemoElement element = (OmemoElement) carbonCopy.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoElement_VAxolotl.NAMESPACE);
if (element == null) {
return;
}
OmemoMessage.Received decrypted;
BareJid sender = carbonCopy.getFrom().asBareJid();
try {
decrypted = decryptMessage(managerGuard, sender, element);
manager.notifyOmemoCarbonCopyReceived(direction, carbonCopy, wrappingMessage, decrypted);
if (decrypted.isPreKeyMessage() && OmemoConfiguration.getCompleteSessionWithEmptyMessage()) {
LOGGER.log(Level.FINE, "Received a preKeyMessage in a carbon copy from " + decrypted.getSenderDevice() + ".\n" + "Complete the session by sending an empty response message.");
try {
sendRatchetUpdate(managerGuard, decrypted.getSenderDevice());
} catch (CannotEstablishOmemoSessionException e) {
throw new AssertionError("Since we successfully received a message, we MUST be able to " + "establish a session. " + e);
} catch (NoSuchAlgorithmException | InterruptedException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
LOGGER.log(Level.WARNING, "Cannot send a ratchet update message.", e);
}
}
} catch (NoRawSessionException e) {
OmemoDevice device = e.getDeviceWithoutSession();
LOGGER.log(Level.WARNING, "No raw session found for contact " + device + ". ", e);
if (OmemoConfiguration.getRepairBrokenSessionsWithPreKeyMessages()) {
repairBrokenSessionWithPreKeyMessage(managerGuard, device);
}
} catch (CorruptedOmemoKeyException | CryptoFailedException e) {
LOGGER.log(Level.WARNING, "Could not decrypt incoming carbon copy: ", e);
}
// Upload fresh bundle.
if (getOmemoStoreBackend().loadOmemoPreKeys(userDevice).size() < OmemoConstants.PRE_KEY_COUNT_PER_BUNDLE) {
LOGGER.log(Level.FINE, "We used up a preKey. Upload a fresh bundle.");
try {
getOmemoStoreBackend().replenishKeys(userDevice);
OmemoBundleElement bundleElement = getOmemoStoreBackend().packOmemoBundle(userDevice);
publishBundle(manager.getConnection(), userDevice, bundleElement);
} catch (CorruptedOmemoKeyException | InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | NotALeafNodeException e) {
LOGGER.log(Level.WARNING, "Could not republish replenished bundle.", e);
}
}
}
}
use of org.jivesoftware.smackx.omemo.element.OmemoBundleElement in project Smack by igniterealtime.
the class OmemoService method buildFreshSessionWithDevice.
/**
* Fetch the bundle of a contact and build a fresh OMEMO session with the contacts device.
* Note that this builds a fresh session, regardless if we have had a session before or not.
*
* @param connection authenticated XMPP connection
* @param userDevice our OmemoDevice
* @param contactsDevice OmemoDevice of a contact.
*
* @throws CannotEstablishOmemoSessionException if we cannot establish a session (because of missing bundle etc.)
* @throws SmackException.NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
* @throws SmackException.NoResponseException if there was no response from the remote entity.
* @throws CorruptedOmemoKeyException if our IdentityKeyPair is corrupted.
*/
void buildFreshSessionWithDevice(XMPPConnection connection, OmemoDevice userDevice, OmemoDevice contactsDevice) throws CannotEstablishOmemoSessionException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, CorruptedOmemoKeyException {
if (contactsDevice.equals(userDevice)) {
// Do not build a session with yourself.
return;
}
OmemoBundleElement bundleElement;
try {
bundleElement = fetchBundle(connection, contactsDevice);
} catch (XMPPException.XMPPErrorException | PubSubException.NotALeafNodeException | PubSubException.NotAPubSubNodeException e) {
throw new CannotEstablishOmemoSessionException(contactsDevice, e);
}
// Select random Bundle
HashMap<Integer, T_Bundle> bundlesList = getOmemoStoreBackend().keyUtil().BUNDLE.bundles(bundleElement, contactsDevice);
int randomIndex = new Random().nextInt(bundlesList.size());
T_Bundle randomPreKeyBundle = new ArrayList<>(bundlesList.values()).get(randomIndex);
// build the session
OmemoManager omemoManager = OmemoManager.getInstanceFor(connection, userDevice.getDeviceId());
processBundle(omemoManager, randomPreKeyBundle, contactsDevice);
}
use of org.jivesoftware.smackx.omemo.element.OmemoBundleElement in project Smack by igniterealtime.
the class OmemoManager method rotateSignedPreKey.
/**
* Rotate the signedPreKey published in our OmemoBundle and republish it. This should be done every now and
* then (7-14 days). The old signedPreKey should be kept for some more time (a month or so) to enable decryption
* of messages that have been sent since the key was changed.
*
* @throws CorruptedOmemoKeyException When the IdentityKeyPair is damaged.
* @throws InterruptedException XMPP error
* @throws XMPPException.XMPPErrorException XMPP error
* @throws SmackException.NotConnectedException XMPP error
* @throws SmackException.NoResponseException XMPP error
* @throws SmackException.NotLoggedInException if the XMPP connection is not authenticated.
* @throws IOException if an I/O error occurred.
* @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
*/
public synchronized void rotateSignedPreKey() throws CorruptedOmemoKeyException, SmackException.NotLoggedInException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, IOException, PubSubException.NotALeafNodeException {
if (!connection().isAuthenticated()) {
throw new SmackException.NotLoggedInException();
}
// generate key
getOmemoService().getOmemoStoreBackend().changeSignedPreKey(getOwnDevice());
// publish
OmemoBundleElement bundle = getOmemoService().getOmemoStoreBackend().packOmemoBundle(getOwnDevice());
OmemoService.publishBundle(connection(), getOwnDevice(), bundle);
}
Aggregations