add automatic session completion

This commit is contained in:
Daniel Gultsch 2023-02-28 15:31:26 +01:00
parent cf17a2ac6d
commit ac2866a682
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
3 changed files with 70 additions and 5 deletions

View file

@ -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();

View file

@ -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);
} }

View file

@ -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