add changes of latest dev

This commit is contained in:
Brice
2021-01-13 16:13:49 +11:00
parent cb5ee74a43
commit 99107d169e
33 changed files with 50848 additions and 46160 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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