add automatic session completion
This commit is contained in:
parent
cf17a2ac6d
commit
ac2866a682
|
@ -96,6 +96,9 @@ public class AxolotlService extends AbstractAccountService {
|
||||||
throws NoSessionException {
|
throws NoSessionException {
|
||||||
final var session = getExistingSession(axolotlAddress);
|
final var session = getExistingSession(axolotlAddress);
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
|
// TODO When receiving a message that is not an OMEMOKeyExchange from a device there is
|
||||||
|
// no session with, clients SHOULD create a session with that device and notify it about
|
||||||
|
// the new session by responding with an empty OMEMO message as per Sending a message.
|
||||||
throw new NoSessionException(
|
throw new NoSessionException(
|
||||||
String.format("No session for %s", axolotlAddress.toString()));
|
String.format("No session for %s", axolotlAddress.toString()));
|
||||||
}
|
}
|
||||||
|
@ -166,15 +169,15 @@ public class AxolotlService extends AbstractAccountService {
|
||||||
new AxolotlAddress(from.asBareJid(), header.getSourceDevice().get()));
|
new AxolotlAddress(from.asBareJid(), header.getSourceDevice().get()));
|
||||||
keyWithAuthTag = session.sessionCipher.decrypt(signalMessage);
|
keyWithAuthTag = session.sessionCipher.decrypt(signalMessage);
|
||||||
}
|
}
|
||||||
if (keyWithAuthTag.length < 32) {
|
|
||||||
throw new OutdatedSenderException(
|
|
||||||
"Key did not contain auth tag. Sender needs to update their OMEMO client");
|
|
||||||
}
|
|
||||||
final var inDeviceList = database.axolotlDao().hasDeviceId(account, session.axolotlAddress);
|
final var inDeviceList = database.axolotlDao().hasDeviceId(account, session.axolotlAddress);
|
||||||
if (payload == null) {
|
if (payload == null) {
|
||||||
return new AxolotlPayload(
|
return new AxolotlPayload(
|
||||||
session.axolotlAddress, session.identityKey, preKeyMessage, inDeviceList, null);
|
session.axolotlAddress, session.identityKey, preKeyMessage, inDeviceList, null);
|
||||||
}
|
}
|
||||||
|
if (keyWithAuthTag.length < 32) {
|
||||||
|
throw new OutdatedSenderException(
|
||||||
|
"Key did not contain auth tag. Sender needs to update their OMEMO client");
|
||||||
|
}
|
||||||
final byte[] key = new byte[16];
|
final byte[] key = new byte[16];
|
||||||
final byte[] authTag = new byte[16];
|
final byte[] authTag = new byte[16];
|
||||||
final byte[] iv = header.getIv();
|
final byte[] iv = header.getIv();
|
||||||
|
|
|
@ -31,7 +31,7 @@ public class EncryptionBuilder {
|
||||||
|
|
||||||
private Long sourceDeviceId;
|
private Long sourceDeviceId;
|
||||||
|
|
||||||
private ArrayList<AxolotlSession> sessions;
|
private final ArrayList<AxolotlSession> sessions = new ArrayList<>();
|
||||||
|
|
||||||
private byte[] payload;
|
private byte[] payload;
|
||||||
|
|
||||||
|
@ -76,6 +76,33 @@ public class EncryptionBuilder {
|
||||||
return encrypted;
|
return encrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Encrypted buildKeyTransport() throws AxolotlEncryptionException {
|
||||||
|
try {
|
||||||
|
return buildKeyTransportOrThrow();
|
||||||
|
} catch (final UntrustedIdentityException e) {
|
||||||
|
throw new AxolotlEncryptionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Encrypted buildKeyTransportOrThrow() throws UntrustedIdentityException {
|
||||||
|
final long sourceDeviceId =
|
||||||
|
Preconditions.checkNotNull(this.sourceDeviceId, "Specify a source device id");
|
||||||
|
Preconditions.checkState(
|
||||||
|
this.payload == null, "A key transport message should not have a payload");
|
||||||
|
// TODO key transport messages in twomemo (omemo:1) use 32 bytes of zeros instead of a key
|
||||||
|
// TODO if we are not using this using this for actual key transport we can do this in siacs
|
||||||
|
// omemo too (and get rid of the IV)
|
||||||
|
final var sessions = ImmutableList.copyOf(this.sessions);
|
||||||
|
final var key = generateKey();
|
||||||
|
final var iv = generateIv();
|
||||||
|
final var header = buildHeader(sessions, key);
|
||||||
|
header.addIv(iv);
|
||||||
|
header.setSourceDevice(sourceDeviceId);
|
||||||
|
final var encrypted = new Encrypted();
|
||||||
|
encrypted.addExtension(header);
|
||||||
|
return encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
public EncryptionBuilder payload(final String payload) {
|
public EncryptionBuilder payload(final String payload) {
|
||||||
this.payload = payload.getBytes(StandardCharsets.UTF_8);
|
this.payload = payload.getBytes(StandardCharsets.UTF_8);
|
||||||
return this;
|
return this;
|
||||||
|
@ -97,6 +124,7 @@ public class EncryptionBuilder {
|
||||||
for (final AxolotlSession session : sessions) {
|
for (final AxolotlSession session : sessions) {
|
||||||
final var cipherMessage = session.sessionCipher.encrypt(keyWithAuthTag);
|
final var cipherMessage = session.sessionCipher.encrypt(keyWithAuthTag);
|
||||||
final var key = header.addExtension(new Key());
|
final var key = header.addExtension(new Key());
|
||||||
|
key.setRemoteDeviceId(session.axolotlAddress.getDeviceId());
|
||||||
key.setContent(cipherMessage.serialize());
|
key.setContent(cipherMessage.serialize());
|
||||||
key.setIsPreKey(cipherMessage.getType() == CiphertextMessage.PREKEY_TYPE);
|
key.setIsPreKey(cipherMessage.getType() == CiphertextMessage.PREKEY_TYPE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import im.conversations.android.xmpp.model.axolotl.Bundle;
|
||||||
import im.conversations.android.xmpp.model.axolotl.DeviceList;
|
import im.conversations.android.xmpp.model.axolotl.DeviceList;
|
||||||
import im.conversations.android.xmpp.model.axolotl.Encrypted;
|
import im.conversations.android.xmpp.model.axolotl.Encrypted;
|
||||||
import im.conversations.android.xmpp.model.pubsub.Items;
|
import im.conversations.android.xmpp.model.pubsub.Items;
|
||||||
|
import im.conversations.android.xmpp.model.stanza.Message;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -503,7 +504,40 @@ public class AxolotlManager extends AbstractManager implements AxolotlService.Po
|
||||||
"fresh session from {}/{}",
|
"fresh session from {}/{}",
|
||||||
axolotlAddress.getJid(),
|
axolotlAddress.getJid(),
|
||||||
axolotlAddress.getDeviceId());
|
axolotlAddress.getDeviceId());
|
||||||
|
// After receiving an OMEMOKeyExchange and successfully building a new session, the
|
||||||
|
// receiving device SHOULD automatically respond with an empty OMEMO message (as per
|
||||||
|
// Sending a message) to the source of the OMEMOKeyExchange. This is to notify the
|
||||||
|
// device that the session initiation was completed successfully and that the device can
|
||||||
|
// stop sending OMEMOKeyExchanges.
|
||||||
|
sendKeyTransportToCompleteSession(axolotlAddress);
|
||||||
}
|
}
|
||||||
|
// TODO republish device bundle
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendKeyTransportToCompleteSession(final AxolotlAddress axolotlAddress) {
|
||||||
|
final var existingSession = axolotlService.getExistingSession(axolotlAddress);
|
||||||
|
if (existingSession == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Encrypted encrypted;
|
||||||
|
try {
|
||||||
|
encrypted =
|
||||||
|
new EncryptionBuilder()
|
||||||
|
.session(existingSession)
|
||||||
|
.sourceDeviceId(signalProtocolStore().getLocalRegistrationId())
|
||||||
|
.buildKeyTransport();
|
||||||
|
} catch (final AxolotlEncryptionException e) {
|
||||||
|
LOGGER.error("Could not create key transport message to complete session", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final var message = new Message(Message.Type.NORMAL);
|
||||||
|
message.setTo(axolotlAddress.getJid());
|
||||||
|
message.addExtension(encrypted);
|
||||||
|
LOGGER.info(
|
||||||
|
"Sending KeyTransport Message to {}/{}",
|
||||||
|
axolotlAddress.getJid(),
|
||||||
|
axolotlAddress.getDeviceId());
|
||||||
|
connection.sendMessagePacket(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in a new issue