mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-11 14:57:45 +00:00
@@ -77,6 +77,7 @@ import org.session.libsignal.service.loki.api.LokiDotNetAPI;
|
||||
import org.session.libsignal.service.loki.api.PushNotificationAPI;
|
||||
import org.session.libsignal.service.loki.api.SignalMessageInfo;
|
||||
import org.session.libsignal.service.loki.api.SnodeAPI;
|
||||
import org.session.libsignal.service.loki.api.crypto.SessionProtocol;
|
||||
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI;
|
||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
|
||||
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
|
||||
@@ -142,6 +143,7 @@ public class SignalServiceMessageSender {
|
||||
private final LokiThreadDatabaseProtocol threadDatabase;
|
||||
private final LokiMessageDatabaseProtocol messageDatabase;
|
||||
private final LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase;
|
||||
private final SessionProtocol sessionProtocolImpl;
|
||||
private final SessionResetProtocol sessionResetImpl;
|
||||
private final LokiUserDatabaseProtocol userDatabase;
|
||||
private final LokiOpenGroupDatabaseProtocol openGroupDatabase;
|
||||
@@ -171,12 +173,13 @@ public class SignalServiceMessageSender {
|
||||
LokiThreadDatabaseProtocol threadDatabase,
|
||||
LokiMessageDatabaseProtocol messageDatabase,
|
||||
LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase,
|
||||
SessionProtocol sessionProtocolImpl,
|
||||
SessionResetProtocol sessionResetImpl,
|
||||
LokiUserDatabaseProtocol userDatabase,
|
||||
LokiOpenGroupDatabaseProtocol openGroupDatabase,
|
||||
Broadcaster broadcaster)
|
||||
{
|
||||
this(urls, new StaticCredentialsProvider(user, password, null), store, userAgent, isMultiDevice, pipe, unidentifiedPipe, eventListener, userPublicKey, apiDatabase, sskDatabase, threadDatabase, messageDatabase, preKeyBundleDatabase, sessionResetImpl, userDatabase, openGroupDatabase, broadcaster);
|
||||
this(urls, new StaticCredentialsProvider(user, password, null), store, userAgent, isMultiDevice, pipe, unidentifiedPipe, eventListener, userPublicKey, apiDatabase, sskDatabase, threadDatabase, messageDatabase, preKeyBundleDatabase, sessionProtocolImpl, sessionResetImpl, userDatabase, openGroupDatabase, broadcaster);
|
||||
}
|
||||
|
||||
public SignalServiceMessageSender(SignalServiceConfiguration urls,
|
||||
@@ -193,6 +196,7 @@ public class SignalServiceMessageSender {
|
||||
LokiThreadDatabaseProtocol threadDatabase,
|
||||
LokiMessageDatabaseProtocol messageDatabase,
|
||||
LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase,
|
||||
SessionProtocol sessionProtocolImpl,
|
||||
SessionResetProtocol sessionResetImpl,
|
||||
LokiUserDatabaseProtocol userDatabase,
|
||||
LokiOpenGroupDatabaseProtocol openGroupDatabase,
|
||||
@@ -211,6 +215,7 @@ public class SignalServiceMessageSender {
|
||||
this.threadDatabase = threadDatabase;
|
||||
this.messageDatabase = messageDatabase;
|
||||
this.preKeyBundleDatabase = preKeyBundleDatabase;
|
||||
this.sessionProtocolImpl = sessionProtocolImpl;
|
||||
this.sessionResetImpl = sessionResetImpl;
|
||||
this.userDatabase = userDatabase;
|
||||
this.openGroupDatabase = openGroupDatabase;
|
||||
@@ -1383,6 +1388,30 @@ public class SignalServiceMessageSender {
|
||||
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)
|
||||
{
|
||||
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);
|
||||
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;
|
||||
@@ -1417,7 +1446,7 @@ public class SignalServiceMessageSender {
|
||||
{
|
||||
int deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||
SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(groupPublicKey, deviceID);
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, store, sskDatabase, sessionResetImpl, null);
|
||||
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) {
|
||||
@@ -1435,7 +1464,7 @@ public class SignalServiceMessageSender {
|
||||
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, null);
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, store, sskDatabase, sessionResetImpl, sessionProtocolImpl, null);
|
||||
|
||||
try {
|
||||
String contactPublicKey = recipient.getNumber();
|
||||
|
@@ -83,6 +83,7 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos.SyncMessa
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.TypingMessage;
|
||||
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.opengroups.PublicChat;
|
||||
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupUtilities;
|
||||
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol;
|
||||
@@ -114,18 +115,21 @@ public class SignalServiceCipher {
|
||||
private final SessionResetProtocol sessionResetProtocol;
|
||||
private final SharedSenderKeysDatabaseProtocol sskDatabase;
|
||||
private final SignalServiceAddress localAddress;
|
||||
private final SessionProtocol sessionProtocolImpl;
|
||||
private final CertificateValidator certificateValidator;
|
||||
|
||||
public SignalServiceCipher(SignalServiceAddress localAddress,
|
||||
SignalProtocolStore signalProtocolStore,
|
||||
SharedSenderKeysDatabaseProtocol sskDatabase,
|
||||
SessionResetProtocol sessionResetProtocol,
|
||||
SessionProtocol sessionProtocolImpl,
|
||||
CertificateValidator certificateValidator)
|
||||
{
|
||||
this.signalProtocolStore = signalProtocolStore;
|
||||
this.sessionResetProtocol = sessionResetProtocol;
|
||||
this.sskDatabase = sskDatabase;
|
||||
this.localAddress = localAddress;
|
||||
this.sessionProtocolImpl = sessionProtocolImpl;
|
||||
this.certificateValidator = certificateValidator;
|
||||
}
|
||||
|
||||
@@ -317,12 +321,23 @@ public class SignalServiceCipher {
|
||||
int sessionVersion;
|
||||
|
||||
if (envelope.isClosedGroupCiphertext()) {
|
||||
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();
|
||||
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();
|
||||
}
|
||||
} else if (envelope.isPreKeySignalMessage()) {
|
||||
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(ciphertext));
|
||||
metadata = new Metadata(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
||||
@@ -332,11 +347,21 @@ public class SignalServiceCipher {
|
||||
metadata = new Metadata(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
||||
sessionVersion = sessionCipher.getSessionVersion();
|
||||
} else if (envelope.isUnidentifiedSender()) {
|
||||
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()));
|
||||
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()));
|
||||
}
|
||||
} else {
|
||||
throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType());
|
||||
}
|
||||
|
@@ -0,0 +1,40 @@
|
||||
package org.session.libsignal.service.loki.api.crypto
|
||||
|
||||
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
||||
|
||||
interface SessionProtocol {
|
||||
|
||||
sealed class Exception(val description: String) : kotlin.Exception(description) {
|
||||
// Encryption
|
||||
object NoUserED25519KeyPair : Exception("Couldn't find user ED25519 key pair.")
|
||||
object SigningFailed : Exception("Couldn't sign message.")
|
||||
object EncryptionFailed : Exception("Couldn't encrypt message.")
|
||||
// Decryption
|
||||
object NoData : Exception("Received an empty envelope.")
|
||||
object InvalidGroupPublicKey : Exception("Invalid group public key.")
|
||||
object NoGroupPrivateKey : Exception("Missing group private key.")
|
||||
object DecryptionFailed : Exception("Couldn't decrypt message.")
|
||||
object InvalidSignature : Exception("Invalid message signature.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts `plaintext` using the Session protocol for `hexEncodedX25519PublicKey`.
|
||||
*
|
||||
* @param plaintext the plaintext to encrypt. Must already be padded.
|
||||
* @param recipientHexEncodedX25519PublicKey the X25519 public key to encrypt for. Could be the Session ID of a user, or the public key of a closed group.
|
||||
*
|
||||
* @return the encrypted message.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param envelope the envelope for which to decrypt the content.
|
||||
*
|
||||
* @return the padded plaintext.
|
||||
*/
|
||||
fun decrypt(envelope: SignalServiceEnvelope): Pair<ByteArray, String>
|
||||
}
|
@@ -9,15 +9,23 @@ import org.session.libsignal.service.api.crypto.SignalServiceCipher
|
||||
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.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, certificateValidator) {
|
||||
: SignalServiceCipher(
|
||||
localAddress,
|
||||
signalProtocolStore,
|
||||
sskDatabase,
|
||||
sessionResetProtocol,
|
||||
sessionProtocolImpl,
|
||||
certificateValidator) {
|
||||
|
||||
private val userPrivateKey get() = signalProtocolStore.identityKeyPair.privateKey.serialize()
|
||||
|
||||
|
Reference in New Issue
Block a user