mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-11 15:07:46 +00:00
add changes of latest dev
This commit is contained in:
@@ -9,6 +9,7 @@ import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.metadata.SealedSessionCipher;
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.SessionBuilder;
|
||||
@@ -95,6 +96,7 @@ import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLin
|
||||
import org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
|
||||
import org.session.libsignal.service.loki.protocol.shelved.syncmessages.SyncMessagesProtocol;
|
||||
import org.session.libsignal.service.loki.utilities.Broadcaster;
|
||||
import org.session.libsignal.service.loki.utilities.HexEncodingKt;
|
||||
import org.session.libsignal.service.loki.utilities.PlaintextOutputStreamFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -116,8 +118,6 @@ import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
import nl.komponents.kovenant.Promise;
|
||||
|
||||
import static org.session.libsignal.libsignal.SessionCipher.SESSION_LOCK;
|
||||
|
||||
/**
|
||||
* The main interface for sending Signal Service messages.
|
||||
*
|
||||
@@ -1168,93 +1168,80 @@ public class SignalServiceMessageSender {
|
||||
{
|
||||
if (recipient.getNumber().equals(userPublicKey)) { return SendMessageResult.success(recipient, false, false); }
|
||||
final SettableFuture<?>[] future = { new SettableFuture<Unit>() };
|
||||
try {
|
||||
OutgoingPushMessageList messages = getEncryptedMessages(socket, recipient, unidentifiedAccess, timestamp, content, online, useFallbackEncryption, isClosedGroup);
|
||||
// Loki - Remove this when we have shared sender keys
|
||||
// ========
|
||||
if (messages.getMessages().isEmpty()) {
|
||||
return SendMessageResult.success(recipient, false, false);
|
||||
}
|
||||
// ========
|
||||
Set<String> userLinkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey);
|
||||
if (sskDatabase.isSSKBasedClosedGroup(recipient.getNumber())) {
|
||||
Log.d("Loki", "Sending message to closed group.") ;
|
||||
} else if (recipient.getNumber().equals(userPublicKey)) {
|
||||
Log.d("Loki", "Sending message to self.");
|
||||
} else if (userLinkedDevices.contains(recipient.getNumber())) {
|
||||
Log.d("Loki", "Sending message to linked device.");
|
||||
} else {
|
||||
Log.d("Loki", "Sending message to " + recipient.getNumber() + ".");
|
||||
}
|
||||
OutgoingPushMessage message = messages.getMessages().get(0);
|
||||
final SignalServiceProtos.Envelope.Type type = SignalServiceProtos.Envelope.Type.valueOf(message.type);
|
||||
final String senderID;
|
||||
if (type == SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT) {
|
||||
senderID = recipient.getNumber();
|
||||
// } else if (type == SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER) {
|
||||
// senderID = "";
|
||||
} else {
|
||||
senderID = userPublicKey;
|
||||
}
|
||||
final int senderDeviceID = (type == SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER) ? 0 : SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||
// Make sure we have a valid ttl; otherwise default to 2 days
|
||||
if (ttl <= 0) { ttl = TTLUtilities.INSTANCE.getFallbackMessageTTL(); }
|
||||
final int regularMessageTTL = TTLUtilities.getTTL(TTLUtilities.MessageType.Regular);
|
||||
final int __ttl = ttl;
|
||||
final SignalMessageInfo messageInfo = new SignalMessageInfo(type, timestamp, senderID, senderDeviceID, message.content, recipient.getNumber(), ttl, false);
|
||||
SnodeAPI.shared.sendSignalMessage(messageInfo).success(new Function1<Set<Promise<Map<?, ?>, Exception>>, Unit>() {
|
||||
|
||||
@Override
|
||||
public Unit invoke(Set<Promise<Map<?, ?>, Exception>> promises) {
|
||||
final boolean[] isSuccess = { false };
|
||||
final int[] promiseCount = {promises.size()};
|
||||
final int[] errorCount = { 0 };
|
||||
for (Promise<Map<?, ?>, Exception> promise : promises) {
|
||||
promise.success(new Function1<Map<?, ?>, Unit>() {
|
||||
|
||||
@Override
|
||||
public Unit invoke(Map<?, ?> map) {
|
||||
if (isSuccess[0]) { return Unit.INSTANCE; } // Succeed as soon as the first promise succeeds
|
||||
if (__ttl == regularMessageTTL) {
|
||||
broadcaster.broadcast("messageSent", timestamp);
|
||||
}
|
||||
isSuccess[0] = true;
|
||||
if (notifyPNServer) {
|
||||
PushNotificationAPI.shared.notify(messageInfo);
|
||||
}
|
||||
@SuppressWarnings("unchecked") SettableFuture<Unit> f = (SettableFuture<Unit>)future[0];
|
||||
f.set(Unit.INSTANCE);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
}).fail(new Function1<Exception, Unit>() {
|
||||
|
||||
@Override
|
||||
public Unit invoke(Exception exception) {
|
||||
errorCount[0] += 1;
|
||||
if (errorCount[0] != promiseCount[0]) { return Unit.INSTANCE; } // Only error out if all promises failed
|
||||
if (__ttl == regularMessageTTL) {
|
||||
broadcaster.broadcast("messageFailed", timestamp);
|
||||
}
|
||||
@SuppressWarnings("unchecked") SettableFuture<Unit> f = (SettableFuture<Unit>)future[0];
|
||||
f.setException(exception);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
}).fail(new Function1<Exception, Unit>() {
|
||||
|
||||
@Override
|
||||
public Unit invoke(Exception exception) {
|
||||
@SuppressWarnings("unchecked") SettableFuture<Unit> f = (SettableFuture<Unit>)future[0];
|
||||
f.setException(exception);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
});
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException(e);
|
||||
OutgoingPushMessageList messages = getSessionProtocolEncryptedMessage(recipient, timestamp, content);
|
||||
// Loki - Remove this when we have shared sender keys
|
||||
// ========
|
||||
if (messages.getMessages().isEmpty()) {
|
||||
return SendMessageResult.success(recipient, false, false);
|
||||
}
|
||||
// ========
|
||||
OutgoingPushMessage message = messages.getMessages().get(0);
|
||||
final SignalServiceProtos.Envelope.Type type = SignalServiceProtos.Envelope.Type.valueOf(message.type);
|
||||
final String senderID;
|
||||
if (type == SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT) {
|
||||
senderID = recipient.getNumber();
|
||||
} else if (type == SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER) {
|
||||
senderID = "";
|
||||
} else {
|
||||
senderID = userPublicKey;
|
||||
}
|
||||
final int senderDeviceID = (type == SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER) ? 0 : SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||
// Make sure we have a valid ttl; otherwise default to 2 days
|
||||
if (ttl <= 0) { ttl = TTLUtilities.INSTANCE.getFallbackMessageTTL(); }
|
||||
final int regularMessageTTL = TTLUtilities.getTTL(TTLUtilities.MessageType.Regular);
|
||||
final int __ttl = ttl;
|
||||
final SignalMessageInfo messageInfo = new SignalMessageInfo(type, timestamp, senderID, senderDeviceID, message.content, recipient.getNumber(), ttl, false);
|
||||
SnodeAPI.shared.sendSignalMessage(messageInfo).success(new Function1<Set<Promise<Map<?, ?>, Exception>>, Unit>() {
|
||||
|
||||
@Override
|
||||
public Unit invoke(Set<Promise<Map<?, ?>, Exception>> promises) {
|
||||
final boolean[] isSuccess = { false };
|
||||
final int[] promiseCount = {promises.size()};
|
||||
final int[] errorCount = { 0 };
|
||||
for (Promise<Map<?, ?>, Exception> promise : promises) {
|
||||
promise.success(new Function1<Map<?, ?>, Unit>() {
|
||||
|
||||
@Override
|
||||
public Unit invoke(Map<?, ?> map) {
|
||||
if (isSuccess[0]) { return Unit.INSTANCE; } // Succeed as soon as the first promise succeeds
|
||||
if (__ttl == regularMessageTTL) {
|
||||
broadcaster.broadcast("messageSent", timestamp);
|
||||
}
|
||||
isSuccess[0] = true;
|
||||
if (notifyPNServer) {
|
||||
PushNotificationAPI.shared.notify(messageInfo);
|
||||
}
|
||||
@SuppressWarnings("unchecked") SettableFuture<Unit> f = (SettableFuture<Unit>)future[0];
|
||||
f.set(Unit.INSTANCE);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
}).fail(new Function1<Exception, Unit>() {
|
||||
|
||||
@Override
|
||||
public Unit invoke(Exception exception) {
|
||||
errorCount[0] += 1;
|
||||
if (errorCount[0] != promiseCount[0]) { return Unit.INSTANCE; } // Only error out if all promises failed
|
||||
if (__ttl == regularMessageTTL) {
|
||||
broadcaster.broadcast("messageFailed", timestamp);
|
||||
}
|
||||
@SuppressWarnings("unchecked") SettableFuture<Unit> f = (SettableFuture<Unit>)future[0];
|
||||
f.setException(exception);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
}).fail(new Function1<Exception, Unit>() {
|
||||
|
||||
@Override
|
||||
public Unit invoke(Exception exception) {
|
||||
@SuppressWarnings("unchecked") SettableFuture<Unit> f = (SettableFuture<Unit>)future[0];
|
||||
f.setException(exception);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
});
|
||||
|
||||
@SuppressWarnings("unchecked") SettableFuture<Unit> f = (SettableFuture<Unit>)future[0];
|
||||
try {
|
||||
f.get(1, TimeUnit.MINUTES);
|
||||
@@ -1347,165 +1334,28 @@ public class SignalServiceMessageSender {
|
||||
return createAttachmentPointer(pointer);
|
||||
}
|
||||
|
||||
private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket,
|
||||
SignalServiceAddress recipient,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
long timestamp,
|
||||
byte[] plaintext,
|
||||
boolean online,
|
||||
boolean useFallbackEncryption,
|
||||
boolean isClosedGroup)
|
||||
throws IOException, InvalidKeyException, UntrustedIdentityException
|
||||
{
|
||||
List<OutgoingPushMessage> messages = new LinkedList<>();
|
||||
|
||||
// Loki - The way this works is:
|
||||
// • Alice sends a session request (i.e. a pre key bundle) to Bob using fallback encryption.
|
||||
// • She may send any number of subsequent messages also encrypted using fallback encryption.
|
||||
// • When Bob receives the session request, he sets up his Signal cipher session locally and sends back a null message,
|
||||
// now encrypted using Signal encryption.
|
||||
// • Alice receives this, sets up her Signal cipher session locally, and sends any subsequent messages
|
||||
// using Signal encryption.
|
||||
|
||||
if (!recipient.equals(localAddress) || unidentifiedAccess.isPresent()) {
|
||||
if (sskDatabase.isSSKBasedClosedGroup(recipient.getNumber()) && unidentifiedAccess.isPresent()) {
|
||||
messages.add(getSSKEncryptedMessage(recipient.getNumber(), unidentifiedAccess.get(), plaintext));
|
||||
// } else if (useFallbackEncryption) {
|
||||
// messages.add(getFallbackCipherEncryptedMessage(recipient.getNumber(), plaintext, unidentifiedAccess));
|
||||
// } else {
|
||||
// OutgoingPushMessage message = getEncryptedMessage(socket, recipient, unidentifiedAccess, plaintext, isClosedGroup);
|
||||
// if (message != null) { // May be null in a closed group context
|
||||
// messages.add(message);
|
||||
// }
|
||||
// }
|
||||
} else {
|
||||
//AC: Use unencrypted messages for private chats.
|
||||
OutgoingPushMessage message = getUnencryptedMessage(plaintext);
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
return new OutgoingPushMessageList(recipient.getNumber(), timestamp, messages, online);
|
||||
}
|
||||
|
||||
private OutgoingPushMessageList getSessionProtocolEncryptedMessages(PushServiceSocket socket,
|
||||
SignalServiceAddress recipient,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
long timestamp,
|
||||
byte[] plaintext,
|
||||
boolean online,
|
||||
boolean useFallbackEncryption,
|
||||
boolean isClosedGroup)
|
||||
private OutgoingPushMessageList getSessionProtocolEncryptedMessage(SignalServiceAddress recipient, long timestamp, byte[] plaintext)
|
||||
{
|
||||
List<OutgoingPushMessage> messages = new LinkedList<>();
|
||||
|
||||
PushTransportDetails transportDetails = new PushTransportDetails(3);
|
||||
String publicKey = recipient.getNumber(); // Could be a contact's public key or the public key of a SSK group
|
||||
byte[] ciphertext = sessionProtocolImpl.encrypt(transportDetails.getPaddedMessageBody(plaintext), publicKey);
|
||||
String body = Base64.encodeBytes(ciphertext);
|
||||
boolean isSSKBasedClosedGroup = sskDatabase.isSSKBasedClosedGroup(publicKey);
|
||||
String encryptionPublicKey;
|
||||
if (isSSKBasedClosedGroup) {
|
||||
ECKeyPair encryptionKeyPair = apiDatabase.getLatestClosedGroupEncryptionKeyPair(publicKey);
|
||||
encryptionPublicKey = HexEncodingKt.getHexEncodedPublicKey(encryptionKeyPair);
|
||||
} else {
|
||||
encryptionPublicKey = publicKey;
|
||||
}
|
||||
byte[] ciphertext = sessionProtocolImpl.encrypt(transportDetails.getPaddedMessageBody(plaintext), encryptionPublicKey);
|
||||
String body = Base64.encodeBytes(ciphertext);
|
||||
int type = isSSKBasedClosedGroup ? SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE :
|
||||
SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE;
|
||||
OutgoingPushMessage message = new OutgoingPushMessage(type, 1, 0, body);
|
||||
messages.add(message);
|
||||
|
||||
return new OutgoingPushMessageList(publicKey, timestamp, messages, online);
|
||||
}
|
||||
|
||||
private OutgoingPushMessage getUnencryptedMessage(byte[] plaintext) {
|
||||
Log.d("Loki", "Bypassing cipher and preparing a plaintext message.");
|
||||
int deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||
PushTransportDetails transportDetails = new PushTransportDetails(FallbackSessionCipher.getSessionVersion());
|
||||
byte[] bytes = transportDetails.getPaddedMessageBody(plaintext);
|
||||
return new OutgoingPushMessage(SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE, deviceID, 0, Base64.encodeBytes(bytes));
|
||||
}
|
||||
|
||||
private OutgoingPushMessage getFallbackCipherEncryptedMessage(String publicKey, byte[] plaintext, Optional<UnidentifiedAccess> unidentifiedAccess)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
Log.d("Loki", "Using fallback cipher.");
|
||||
int deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||
SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(publicKey, deviceID);
|
||||
byte[] userPrivateKey = store.getIdentityKeyPair().getPrivateKey().serialize();
|
||||
FallbackSessionCipher cipher = new FallbackSessionCipher(userPrivateKey, publicKey);
|
||||
PushTransportDetails transportDetails = new PushTransportDetails(FallbackSessionCipher.getSessionVersion());
|
||||
byte[] bytes = cipher.encrypt(transportDetails.getPaddedMessageBody(plaintext));
|
||||
if (bytes == null) { bytes = new byte[0]; }
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
SealedSessionCipher sealedSessionCipher = new SealedSessionCipher(store, null, signalProtocolAddress);
|
||||
FallbackMessage message = new FallbackMessage(bytes);
|
||||
byte[] ciphertext = sealedSessionCipher.encrypt(signalProtocolAddress, unidentifiedAccess.get().getUnidentifiedCertificate(), message);
|
||||
return new OutgoingPushMessage(SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE, deviceID, 0, Base64.encodeBytes(ciphertext));
|
||||
} else {
|
||||
return new OutgoingPushMessage(SignalServiceProtos.Envelope.Type.FALLBACK_MESSAGE_VALUE, deviceID, 0, Base64.encodeBytes(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
private OutgoingPushMessage getSSKEncryptedMessage(String groupPublicKey, UnidentifiedAccess unidentifiedAccess, byte[] plaintext)
|
||||
throws InvalidKeyException, UntrustedIdentityException, IOException
|
||||
{
|
||||
int deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||
SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(groupPublicKey, deviceID);
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, store, sskDatabase, sessionResetImpl, sessionProtocolImpl, null);
|
||||
try {
|
||||
return cipher.encrypt(signalProtocolAddress, Optional.of(unidentifiedAccess), plaintext);
|
||||
} catch (org.session.libsignal.libsignal.UntrustedIdentityException e) {
|
||||
throw new UntrustedIdentityException("Untrusted identity", groupPublicKey, e.getUntrustedIdentity());
|
||||
}
|
||||
}
|
||||
|
||||
private OutgoingPushMessage getEncryptedMessage(PushServiceSocket socket,
|
||||
SignalServiceAddress recipient,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
byte[] plaintext,
|
||||
boolean isClosedGroup)
|
||||
throws IOException, InvalidKeyException, UntrustedIdentityException
|
||||
{
|
||||
Log.d("Loki", "Using Signal cipher.");
|
||||
int deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||
SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(recipient.getNumber(), deviceID);
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, store, sskDatabase, sessionResetImpl, sessionProtocolImpl, null);
|
||||
|
||||
try {
|
||||
String contactPublicKey = recipient.getNumber();
|
||||
PreKeyBundle preKeyBundle = preKeyBundleDatabase.getPreKeyBundle(contactPublicKey);
|
||||
if (preKeyBundle == null) {
|
||||
if (!store.containsSession(signalProtocolAddress)) {
|
||||
SessionManagementProtocol.shared.repairSessionIfNeeded(recipient, isClosedGroup);
|
||||
// Loki - Remove this when we have shared sender keys
|
||||
// ========
|
||||
if (SessionManagementProtocol.shared.shouldIgnoreMissingPreKeyBundleException(isClosedGroup)) {
|
||||
return null;
|
||||
}
|
||||
// ========
|
||||
throw new InvalidKeyException("Pre key bundle not found for: " + recipient.getNumber() + ".");
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
SignalProtocolAddress address = new SignalProtocolAddress(contactPublicKey, preKeyBundle.getDeviceId());
|
||||
SessionBuilder sessionBuilder = new SessionBuilder(store, address);
|
||||
sessionBuilder.process(preKeyBundle);
|
||||
// Loki - Discard the pre key bundle once the session has been established
|
||||
preKeyBundleDatabase.removePreKeyBundle(contactPublicKey);
|
||||
} catch (org.session.libsignal.libsignal.UntrustedIdentityException e) {
|
||||
throw new UntrustedIdentityException("Untrusted identity key", recipient.getNumber(), preKeyBundle.getIdentityKey());
|
||||
}
|
||||
if (eventListener.isPresent()) {
|
||||
eventListener.get().onSecurityEvent(recipient);
|
||||
}
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
// Loki - Ensure all session processing has finished
|
||||
synchronized (SESSION_LOCK) {
|
||||
try {
|
||||
return cipher.encrypt(signalProtocolAddress, unidentifiedAccess, plaintext);
|
||||
} catch (org.session.libsignal.libsignal.UntrustedIdentityException e) {
|
||||
throw new UntrustedIdentityException("Untrusted on send", recipient.getNumber(), e.getUntrustedIdentity());
|
||||
}
|
||||
}
|
||||
return new OutgoingPushMessageList(publicKey, timestamp, messages, false);
|
||||
}
|
||||
|
||||
private Optional<UnidentifiedAccess> getTargetUnidentifiedAccess(Optional<UnidentifiedAccessPair> unidentifiedAccess) {
|
||||
|
@@ -9,6 +9,7 @@ package org.session.libsignal.service.api.crypto;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.metadata.InvalidMetadataMessageException;
|
||||
import org.session.libsignal.metadata.InvalidMetadataVersionException;
|
||||
import org.session.libsignal.metadata.ProtocolDuplicateMessageException;
|
||||
@@ -84,7 +85,9 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos.TypingMes
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.Verified;
|
||||
import org.session.libsignal.service.internal.util.Base64;
|
||||
import org.session.libsignal.service.loki.api.crypto.SessionProtocol;
|
||||
import org.session.libsignal.service.loki.api.crypto.SessionProtocolUtilities;
|
||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
|
||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
|
||||
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupUtilities;
|
||||
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol;
|
||||
import org.session.libsignal.service.loki.protocol.sessionmanagement.PreKeyBundleMessage;
|
||||
@@ -116,6 +119,7 @@ public class SignalServiceCipher {
|
||||
private final SharedSenderKeysDatabaseProtocol sskDatabase;
|
||||
private final SignalServiceAddress localAddress;
|
||||
private final SessionProtocol sessionProtocolImpl;
|
||||
private final LokiAPIDatabaseProtocol apiDB;
|
||||
private final CertificateValidator certificateValidator;
|
||||
|
||||
public SignalServiceCipher(SignalServiceAddress localAddress,
|
||||
@@ -123,6 +127,7 @@ public class SignalServiceCipher {
|
||||
SharedSenderKeysDatabaseProtocol sskDatabase,
|
||||
SessionResetProtocol sessionResetProtocol,
|
||||
SessionProtocol sessionProtocolImpl,
|
||||
LokiAPIDatabaseProtocol apiDB,
|
||||
CertificateValidator certificateValidator)
|
||||
{
|
||||
this.signalProtocolStore = signalProtocolStore;
|
||||
@@ -130,6 +135,7 @@ public class SignalServiceCipher {
|
||||
this.sskDatabase = sskDatabase;
|
||||
this.localAddress = localAddress;
|
||||
this.sessionProtocolImpl = sessionProtocolImpl;
|
||||
this.apiDB = apiDB;
|
||||
this.certificateValidator = certificateValidator;
|
||||
}
|
||||
|
||||
@@ -190,7 +196,7 @@ public class SignalServiceCipher {
|
||||
ProtocolUntrustedIdentityException, ProtocolNoSessionException,
|
||||
ProtocolInvalidVersionException, ProtocolInvalidMessageException,
|
||||
ProtocolInvalidKeyException, ProtocolDuplicateMessageException,
|
||||
SelfSendException, IOException
|
||||
SelfSendException, IOException, SessionProtocol.Exception
|
||||
|
||||
{
|
||||
try {
|
||||
@@ -309,7 +315,7 @@ public class SignalServiceCipher {
|
||||
ProtocolLegacyMessageException, ProtocolInvalidKeyException,
|
||||
ProtocolInvalidVersionException, ProtocolInvalidMessageException,
|
||||
ProtocolInvalidKeyIdException, ProtocolNoSessionException,
|
||||
SelfSendException, IOException
|
||||
SelfSendException, IOException, SessionProtocol.Exception
|
||||
{
|
||||
try {
|
||||
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSource(), envelope.getSourceDevice());
|
||||
@@ -321,23 +327,13 @@ public class SignalServiceCipher {
|
||||
int sessionVersion;
|
||||
|
||||
if (envelope.isClosedGroupCiphertext()) {
|
||||
try {
|
||||
// Try the Session protocol
|
||||
kotlin.Pair<byte[], String> plaintextAndSenderPublicKey = sessionProtocolImpl.decrypt(envelope);
|
||||
paddedMessage = plaintextAndSenderPublicKey.getFirst();
|
||||
String senderPublicKey = plaintextAndSenderPublicKey.getSecond();
|
||||
if (senderPublicKey.equals(localAddress.getNumber())) { throw new SelfSendException(); } // Will be caught and ignored in PushDecryptJob
|
||||
metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false);
|
||||
sessionVersion = sessionCipher.getSessionVersion();
|
||||
} catch (Exception exception) {
|
||||
// Fall back on shared sender keys
|
||||
Pair<byte[], String> plaintextAndSenderPublicKey = ClosedGroupUtilities.decrypt(envelope);
|
||||
String senderPublicKey = plaintextAndSenderPublicKey.second();
|
||||
if (senderPublicKey.equals(localAddress.getNumber())) { throw new SelfSendException(); } // Will be caught and ignored in PushDecryptJob
|
||||
paddedMessage = plaintextAndSenderPublicKey.first();
|
||||
metadata = new Metadata(senderPublicKey, envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
||||
sessionVersion = sessionCipher.getSessionVersion();
|
||||
}
|
||||
String groupPublicKey = envelope.getSource();
|
||||
kotlin.Pair<byte[], String> plaintextAndSenderPublicKey = SessionProtocolUtilities.INSTANCE.decryptClosedGroupCiphertext(ciphertext, groupPublicKey, apiDB, sessionProtocolImpl);
|
||||
paddedMessage = plaintextAndSenderPublicKey.getFirst();
|
||||
String senderPublicKey = plaintextAndSenderPublicKey.getSecond();
|
||||
if (senderPublicKey.equals(localAddress.getNumber())) { throw new SelfSendException(); } // Will be caught and ignored in PushDecryptJob
|
||||
metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false);
|
||||
sessionVersion = sessionCipher.getSessionVersion();
|
||||
} else if (envelope.isPreKeySignalMessage()) {
|
||||
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(ciphertext));
|
||||
metadata = new Metadata(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
||||
@@ -347,21 +343,12 @@ public class SignalServiceCipher {
|
||||
metadata = new Metadata(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
||||
sessionVersion = sessionCipher.getSessionVersion();
|
||||
} else if (envelope.isUnidentifiedSender()) {
|
||||
try {
|
||||
// Try the Session protocol
|
||||
kotlin.Pair<byte[], String> plaintextAndSenderPublicKey = sessionProtocolImpl.decrypt(envelope);
|
||||
paddedMessage = plaintextAndSenderPublicKey.getFirst();
|
||||
String senderPublicKey = plaintextAndSenderPublicKey.getSecond();
|
||||
metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false);
|
||||
sessionVersion = sealedSessionCipher.getSessionVersion(new SignalProtocolAddress(metadata.getSender(), metadata.getSenderDevice()));
|
||||
} catch (Exception exception) {
|
||||
// Fall back on the Signal protocol
|
||||
Pair<SignalProtocolAddress, Pair<Integer, byte[]>> results = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerTimestamp(), envelope.getSource());
|
||||
Pair<Integer, byte[]> data = results.second();
|
||||
paddedMessage = data.second();
|
||||
metadata = new Metadata(results.first().getName(), results.first().getDeviceId(), envelope.getTimestamp(), false);
|
||||
sessionVersion = sealedSessionCipher.getSessionVersion(new SignalProtocolAddress(metadata.getSender(), metadata.getSenderDevice()));
|
||||
}
|
||||
ECKeyPair userX25519KeyPair = apiDB.getUserX25519KeyPair();
|
||||
kotlin.Pair<byte[], String> plaintextAndSenderPublicKey = sessionProtocolImpl.decrypt(ciphertext, userX25519KeyPair);
|
||||
paddedMessage = plaintextAndSenderPublicKey.getFirst();
|
||||
String senderPublicKey = plaintextAndSenderPublicKey.getSecond();
|
||||
metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false);
|
||||
sessionVersion = sealedSessionCipher.getSessionVersion(new SignalProtocolAddress(metadata.getSender(), metadata.getSenderDevice()));
|
||||
} else {
|
||||
throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType());
|
||||
}
|
||||
@@ -400,6 +387,7 @@ public class SignalServiceCipher {
|
||||
List<Preview> previews = createPreviews(content);
|
||||
Sticker sticker = createSticker(content);
|
||||
ClosedGroupUpdate closedGroupUpdate = content.getClosedGroupUpdate();
|
||||
SignalServiceProtos.ClosedGroupUpdateV2 closedGroupUpdateV2 = content.getClosedGroupUpdateV2();
|
||||
boolean isDeviceUnlinkingRequest = ((content.getFlags() & DataMessage.Flags.DEVICE_UNLINKING_REQUEST_VALUE) != 0);
|
||||
|
||||
for (AttachmentPointer pointer : content.getAttachmentsList()) {
|
||||
@@ -428,6 +416,7 @@ public class SignalServiceCipher {
|
||||
null,
|
||||
null,
|
||||
closedGroupUpdate,
|
||||
closedGroupUpdateV2,
|
||||
isDeviceUnlinkingRequest);
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,7 @@ import org.session.libsignal.libsignal.state.PreKeyBundle;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.api.messages.shared.SharedContact;
|
||||
import org.session.libsignal.service.api.push.SignalServiceAddress;
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.ClosedGroupUpdateV2;
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.ClosedGroupUpdate;
|
||||
import org.session.libsignal.service.loki.protocol.meta.TTLUtilities;
|
||||
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink;
|
||||
@@ -38,6 +39,7 @@ public class SignalServiceDataMessage {
|
||||
private final Optional<PreKeyBundle> preKeyBundle;
|
||||
private final Optional<DeviceLink> deviceLink;
|
||||
private final Optional<ClosedGroupUpdate> closedGroupUpdate;
|
||||
private final Optional<ClosedGroupUpdateV2> closedGroupUpdateV2;
|
||||
private final boolean isDeviceUnlinkingRequest;
|
||||
|
||||
/**
|
||||
@@ -132,7 +134,7 @@ public class SignalServiceDataMessage {
|
||||
Quote quote, List<SharedContact> sharedContacts, List<Preview> previews,
|
||||
Sticker sticker)
|
||||
{
|
||||
this(timestamp, group, attachments, body, endSession, expiresInSeconds, expirationUpdate, profileKey, profileKeyUpdate, quote, sharedContacts, previews, sticker, null, null, null, false);
|
||||
this(timestamp, group, attachments, body, endSession, expiresInSeconds, expirationUpdate, profileKey, profileKeyUpdate, quote, sharedContacts, previews, sticker, null, null, null, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,7 +154,8 @@ public class SignalServiceDataMessage {
|
||||
boolean expirationUpdate, byte[] profileKey, boolean profileKeyUpdate,
|
||||
Quote quote, List<SharedContact> sharedContacts, List<Preview> previews,
|
||||
Sticker sticker, PreKeyBundle preKeyBundle, DeviceLink deviceLink,
|
||||
ClosedGroupUpdate closedGroupUpdate, boolean isDeviceUnlinkingRequest)
|
||||
ClosedGroupUpdate closedGroupUpdate, ClosedGroupUpdateV2 closedGroupUpdateV2,
|
||||
boolean isDeviceUnlinkingRequest)
|
||||
{
|
||||
this.timestamp = timestamp;
|
||||
this.body = Optional.fromNullable(body);
|
||||
@@ -167,6 +170,7 @@ public class SignalServiceDataMessage {
|
||||
this.preKeyBundle = Optional.fromNullable(preKeyBundle);
|
||||
this.deviceLink = Optional.fromNullable(deviceLink);
|
||||
this.closedGroupUpdate = Optional.fromNullable(closedGroupUpdate);
|
||||
this.closedGroupUpdateV2 = Optional.fromNullable(closedGroupUpdateV2);
|
||||
this.isDeviceUnlinkingRequest = isDeviceUnlinkingRequest;
|
||||
|
||||
if (attachments != null && !attachments.isEmpty()) {
|
||||
@@ -269,6 +273,8 @@ public class SignalServiceDataMessage {
|
||||
|
||||
public Optional<ClosedGroupUpdate> getClosedGroupUpdate() { return closedGroupUpdate; }
|
||||
|
||||
public Optional<ClosedGroupUpdateV2> getClosedGroupUpdateV2() { return closedGroupUpdateV2; }
|
||||
|
||||
public Optional<PreKeyBundle> getPreKeyBundle() { return preKeyBundle; }
|
||||
|
||||
public Optional<DeviceLink> getDeviceLink() { return deviceLink; }
|
||||
@@ -405,12 +411,13 @@ public class SignalServiceDataMessage {
|
||||
|
||||
public SignalServiceDataMessage build() {
|
||||
if (timestamp == 0) timestamp = System.currentTimeMillis();
|
||||
// closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob)
|
||||
// closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob)
|
||||
return new SignalServiceDataMessage(timestamp, group, attachments, body, endSession,
|
||||
expiresInSeconds, expirationUpdate, profileKey,
|
||||
profileKeyUpdate, quote, sharedContacts, previews,
|
||||
sticker, preKeyBundle, deviceLink,
|
||||
null, isDeviceUnlinkingRequest);
|
||||
null, null,
|
||||
isDeviceUnlinkingRequest);
|
||||
}
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
package org.session.libsignal.service.loki.api.crypto
|
||||
|
||||
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
||||
|
||||
interface SessionProtocol {
|
||||
|
||||
@@ -12,7 +13,7 @@ interface SessionProtocol {
|
||||
// Decryption
|
||||
object NoData : Exception("Received an empty envelope.")
|
||||
object InvalidGroupPublicKey : Exception("Invalid group public key.")
|
||||
object NoGroupPrivateKey : Exception("Missing group private key.")
|
||||
object NoGroupKeyPair : Exception("Missing group key pair.")
|
||||
object DecryptionFailed : Exception("Couldn't decrypt message.")
|
||||
object InvalidSignature : Exception("Invalid message signature.")
|
||||
}
|
||||
@@ -28,13 +29,36 @@ interface SessionProtocol {
|
||||
fun encrypt(plaintext: ByteArray, recipientHexEncodedX25519PublicKey: String): ByteArray
|
||||
|
||||
/**
|
||||
* Decrypts `envelope.content` using the Session protocol. If the envelope type is `UNIDENTIFIED_SENDER` the message is assumed to be a one-to-one
|
||||
* message. If the envelope type is `CLOSED_GROUP_CIPHERTEXT` the message is assumed to be a closed group message. In the latter case `envelope.source`
|
||||
* must be set to the closed group's public key.
|
||||
* Decrypts `ciphertext` using the Session protocol and `x25519KeyPair`.
|
||||
*
|
||||
* @param envelope the envelope for which to decrypt the content.
|
||||
* @param ciphertext the data to decrypt.
|
||||
* @param x25519KeyPair the key pair to use for decryption. This could be the current user's key pair, or the key pair of a closed group.
|
||||
*
|
||||
* @return the padded plaintext.
|
||||
*/
|
||||
fun decrypt(envelope: SignalServiceEnvelope): Pair<ByteArray, String>
|
||||
fun decrypt(ciphertext: ByteArray, x25519KeyPair: ECKeyPair): Pair<ByteArray, String>
|
||||
}
|
||||
|
||||
object SessionProtocolUtilities {
|
||||
|
||||
fun decryptClosedGroupCiphertext(ciphertext: ByteArray, groupPublicKey: String, apiDB: LokiAPIDatabaseProtocol, sessionProtocolImpl: SessionProtocol): Pair<ByteArray, String> {
|
||||
val encryptionKeyPairs = apiDB.getClosedGroupEncryptionKeyPairs(groupPublicKey).toMutableList()
|
||||
if (encryptionKeyPairs.isEmpty()) { throw SessionProtocol.Exception.NoGroupKeyPair }
|
||||
// Loop through all known group key pairs in reverse order (i.e. try the latest key pair first (which'll more than
|
||||
// likely be the one we want) but try older ones in case that didn't work)
|
||||
var encryptionKeyPair = encryptionKeyPairs.removeAt(encryptionKeyPairs.lastIndex)
|
||||
fun decrypt(): Pair<ByteArray, String> {
|
||||
try {
|
||||
return sessionProtocolImpl.decrypt(ciphertext, encryptionKeyPair)
|
||||
} catch(exception: Exception) {
|
||||
if (encryptionKeyPairs.isNotEmpty()) {
|
||||
encryptionKeyPair = encryptionKeyPairs.removeAt(encryptionKeyPairs.lastIndex)
|
||||
return decrypt()
|
||||
} else {
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
return decrypt()
|
||||
}
|
||||
}
|
@@ -10,22 +10,11 @@ import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
||||
import org.session.libsignal.service.api.push.SignalServiceAddress
|
||||
import org.session.libsignal.service.internal.push.PushTransportDetails
|
||||
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
|
||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol
|
||||
|
||||
class LokiServiceCipher(
|
||||
localAddress: SignalServiceAddress,
|
||||
private val signalProtocolStore: SignalProtocolStore,
|
||||
private val sskDatabase: SharedSenderKeysDatabaseProtocol,
|
||||
sessionProtocolImpl: SessionProtocol,
|
||||
sessionResetProtocol: SessionResetProtocol,
|
||||
certificateValidator: CertificateValidator?)
|
||||
: SignalServiceCipher(
|
||||
localAddress,
|
||||
signalProtocolStore,
|
||||
sskDatabase,
|
||||
sessionResetProtocol,
|
||||
sessionProtocolImpl,
|
||||
certificateValidator) {
|
||||
class LokiServiceCipher(localAddress: SignalServiceAddress, private val signalProtocolStore: SignalProtocolStore, private val sskDatabase: SharedSenderKeysDatabaseProtocol, sessionProtocolImpl: SessionProtocol, sessionResetProtocol: SessionResetProtocol, apiDB: LokiAPIDatabaseProtocol, certificateValidator: CertificateValidator?) : SignalServiceCipher(localAddress, signalProtocolStore, sskDatabase, sessionResetProtocol, sessionProtocolImpl, apiDB, certificateValidator) {
|
||||
|
||||
|
||||
private val userPrivateKey get() = signalProtocolStore.identityKeyPair.privateKey.serialize()
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package org.session.libsignal.service.loki.database
|
||||
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||
import org.session.libsignal.service.loki.api.Snode
|
||||
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink
|
||||
import java.util.*
|
||||
@@ -34,7 +35,9 @@ interface LokiAPIDatabaseProtocol {
|
||||
fun getOpenGroupProfilePictureURL(group: Long, server: String): String?
|
||||
fun getLastSnodePoolRefreshDate(): Date?
|
||||
fun setLastSnodePoolRefreshDate(newValue: Date)
|
||||
|
||||
fun getUserX25519KeyPair(): ECKeyPair
|
||||
fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): List<ECKeyPair>
|
||||
fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair?
|
||||
|
||||
// region Deprecated
|
||||
fun getDeviceLinks(publicKey: String): Set<DeviceLink>
|
||||
|
@@ -25,7 +25,7 @@ public object TTLUtilities {
|
||||
val dayInMs = 24 * hourInMs
|
||||
return when (messageType) {
|
||||
// Unimportant control messages
|
||||
MessageType.Address, MessageType.Call, MessageType.TypingIndicator, MessageType.Verified -> 1 * minuteInMs
|
||||
MessageType.Address, MessageType.Call, MessageType.TypingIndicator, MessageType.Verified -> 20 * 1000
|
||||
// Somewhat important control messages
|
||||
MessageType.DeviceLink -> 1 * hourInMs
|
||||
// Important control messages
|
||||
|
Reference in New Issue
Block a user