mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-13 22:07:37 +00:00
Implement Session protocol
This commit is contained in:
@@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
|
||||
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
|
||||
import org.thoughtcrime.securesms.loki.protocol.shelved.MultiDeviceOpenGroupUpdateJob;
|
||||
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
||||
@@ -157,6 +158,7 @@ public class SignalCommunicationModule {
|
||||
DatabaseFactory.getLokiThreadDatabase(context),
|
||||
DatabaseFactory.getLokiMessageDatabase(context),
|
||||
DatabaseFactory.getLokiPreKeyBundleDatabase(context),
|
||||
new SessionProtocolImpl(context),
|
||||
new SessionResetImplementation(context),
|
||||
DatabaseFactory.getLokiUserDatabase(context),
|
||||
DatabaseFactory.getGroupDatabase(context),
|
||||
|
@@ -65,6 +65,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
||||
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
|
||||
@@ -259,7 +260,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
|
||||
SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
|
||||
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
|
||||
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, DatabaseFactory.getSSKDatabase(context), sessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
|
||||
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, DatabaseFactory.getSSKDatabase(context), new SessionProtocolImpl(context), sessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
|
||||
|
||||
SignalServiceContent content = cipher.decrypt(envelope);
|
||||
|
||||
|
101
src/org/thoughtcrime/securesms/loki/api/SessionProtocol.kt
Normal file
101
src/org/thoughtcrime/securesms/loki/api/SessionProtocol.kt
Normal file
@@ -0,0 +1,101 @@
|
||||
package org.thoughtcrime.securesms.loki.api
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.goterl.lazycode.lazysodium.LazySodiumAndroid
|
||||
import com.goterl.lazycode.lazysodium.SodiumAndroid
|
||||
import com.goterl.lazycode.lazysodium.interfaces.Box
|
||||
import com.goterl.lazycode.lazysodium.interfaces.Sign
|
||||
import com.goterl.lazycode.lazysodium.utils.KeyPair
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.libsignal.util.Hex
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE
|
||||
import org.whispersystems.signalservice.loki.api.SnodeAPI
|
||||
import org.whispersystems.signalservice.loki.api.crypto.SessionProtocol
|
||||
import org.whispersystems.signalservice.loki.utilities.removing05PrefixIfNeeded
|
||||
import org.whispersystems.signalservice.loki.utilities.toHexString
|
||||
|
||||
class SessionProtocolImpl(private val context: Context) : SessionProtocol {
|
||||
|
||||
override fun encrypt(plaintext: ByteArray, recipientHexEncodedX25519PublicKey: String): ByteArray {
|
||||
val userED25519KeyPair = KeyPairUtilities.getUserED25519KeyPair(context) ?: throw SessionProtocol.Exception.NoUserED25519KeyPair
|
||||
val recipientX25519PublicKey = Hex.fromStringCondensed(recipientHexEncodedX25519PublicKey.removing05PrefixIfNeeded())
|
||||
val sodium = LazySodiumAndroid(SodiumAndroid())
|
||||
|
||||
val verificationData = plaintext + userED25519KeyPair.publicKey.asBytes + recipientX25519PublicKey
|
||||
val signature = ByteArray(Sign.BYTES)
|
||||
try {
|
||||
sodium.cryptoSignDetached(signature, verificationData, verificationData.size.toLong(), userED25519KeyPair.secretKey.asBytes)
|
||||
} catch (exception: Exception) {
|
||||
Log.d("Loki", "Couldn't sign message due to error: $exception.")
|
||||
throw SessionProtocol.Exception.SigningFailed
|
||||
}
|
||||
val plaintextWithMetadata = plaintext + userED25519KeyPair.publicKey.asBytes + signature
|
||||
val ciphertext = ByteArray(plaintextWithMetadata.size + Box.SEALBYTES)
|
||||
try {
|
||||
sodium.cryptoBoxSeal(ciphertext, plaintextWithMetadata, plaintextWithMetadata.size.toLong(), recipientX25519PublicKey)
|
||||
} catch (exception: Exception) {
|
||||
Log.d("Loki", "Couldn't encrypt message due to error: $exception.")
|
||||
throw SessionProtocol.Exception.EncryptionFailed
|
||||
}
|
||||
|
||||
return ciphertext
|
||||
}
|
||||
|
||||
override fun decrypt(envelope: SignalServiceEnvelope): Pair<ByteArray, String> {
|
||||
val ciphertext = envelope.content ?: throw SessionProtocol.Exception.NoData
|
||||
val recipientX25519PrivateKey: ByteArray
|
||||
val recipientX25519PublicKey: ByteArray
|
||||
when (envelope.type) {
|
||||
UNIDENTIFIED_SENDER_VALUE -> {
|
||||
recipientX25519PrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
||||
recipientX25519PublicKey = Hex.fromStringCondensed(TextSecurePreferences.getLocalNumber(context).removing05PrefixIfNeeded())
|
||||
}
|
||||
CLOSED_GROUP_CIPHERTEXT_VALUE -> {
|
||||
val hexEncodedGroupPublicKey = envelope.source
|
||||
val sskDB = DatabaseFactory.getSSKDatabase(context)
|
||||
if (!sskDB.isSSKBasedClosedGroup(hexEncodedGroupPublicKey)) { throw SessionProtocol.Exception.InvalidGroupPublicKey }
|
||||
val hexEncodedGroupPrivateKey = sskDB.getClosedGroupPrivateKey(hexEncodedGroupPublicKey) ?: throw SessionProtocol.Exception.NoGroupPrivateKey
|
||||
recipientX25519PrivateKey = Hex.fromStringCondensed(hexEncodedGroupPrivateKey)
|
||||
recipientX25519PublicKey = Hex.fromStringCondensed(hexEncodedGroupPublicKey.removing05PrefixIfNeeded())
|
||||
}
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
val sodium = LazySodiumAndroid(SodiumAndroid())
|
||||
val signatureSize = Sign.BYTES
|
||||
val ed25519PublicKeySize = Sign.PUBLICKEYBYTES
|
||||
|
||||
// 1. ) Decrypt the message
|
||||
val plaintextWithMetadata = ByteArray(ciphertext.size - Box.SEALBYTES)
|
||||
try {
|
||||
sodium.cryptoBoxSealOpen(plaintextWithMetadata, ciphertext, ciphertext.size.toLong(), recipientX25519PublicKey, recipientX25519PrivateKey)
|
||||
} catch (exception: Exception) {
|
||||
Log.d("Loki", "Couldn't decrypt message due to error: $exception.")
|
||||
throw SessionProtocol.Exception.DecryptionFailed
|
||||
}
|
||||
if (plaintextWithMetadata.size <= (signatureSize + ed25519PublicKeySize)) { throw SessionProtocol.Exception.DecryptionFailed }
|
||||
// 2. ) Get the message parts
|
||||
val signature = plaintextWithMetadata.sliceArray(plaintextWithMetadata.size - signatureSize until plaintextWithMetadata.size)
|
||||
val senderED25519PublicKey = plaintextWithMetadata.sliceArray(plaintextWithMetadata.size - (signatureSize + ed25519PublicKeySize) until plaintextWithMetadata.size - signatureSize)
|
||||
val plaintext = plaintextWithMetadata.sliceArray(0 until plaintextWithMetadata.size - (signatureSize + ed25519PublicKeySize))
|
||||
// 3. ) Verify the signature
|
||||
val verificationData = (plaintext + senderED25519PublicKey + recipientX25519PublicKey)
|
||||
try {
|
||||
val isValid = sodium.cryptoSignVerifyDetached(signature, verificationData, verificationData.size, senderED25519PublicKey)
|
||||
if (!isValid) { throw SessionProtocol.Exception.InvalidSignature }
|
||||
} catch (exception: Exception) {
|
||||
Log.d("Loki", "Couldn't verify message signature due to error: $exception.")
|
||||
throw SessionProtocol.Exception.InvalidSignature
|
||||
}
|
||||
// 4. ) Get the sender's X25519 public key
|
||||
val senderX25519PublicKey = ByteArray(Sign.CURVE25519_PUBLICKEYBYTES)
|
||||
sodium.convertPublicKeyEd25519ToCurve25519(senderX25519PublicKey, senderED25519PublicKey)
|
||||
|
||||
return Pair(plaintext, "05" + senderX25519PublicKey.toHexString())
|
||||
}
|
||||
}
|
@@ -3,13 +3,18 @@ package org.thoughtcrime.securesms.loki.database
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.goterl.lazycode.lazysodium.utils.Key
|
||||
import com.goterl.lazycode.lazysodium.utils.KeyPair
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.database.Database
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
|
||||
import org.whispersystems.signalservice.loki.api.Snode
|
||||
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink
|
||||
import org.whispersystems.signalservice.loki.utilities.toHexString
|
||||
import java.util.*
|
||||
|
||||
class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiAPIDatabaseProtocol {
|
||||
|
@@ -1,8 +1,10 @@
|
||||
package org.thoughtcrime.securesms.loki.utilities
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.goterl.lazycode.lazysodium.LazySodiumAndroid
|
||||
import com.goterl.lazycode.lazysodium.SodiumAndroid
|
||||
import com.goterl.lazycode.lazysodium.utils.Key
|
||||
import com.goterl.lazycode.lazysodium.utils.KeyPair
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.util.Base64
|
||||
@@ -11,6 +13,7 @@ import org.whispersystems.curve25519.Curve25519
|
||||
import org.whispersystems.libsignal.ecc.DjbECPrivateKey
|
||||
import org.whispersystems.libsignal.ecc.DjbECPublicKey
|
||||
import org.whispersystems.libsignal.ecc.ECKeyPair
|
||||
import org.whispersystems.signalservice.loki.utilities.toHexString
|
||||
|
||||
object KeyPairUtilities {
|
||||
|
||||
@@ -49,4 +52,12 @@ object KeyPairUtilities {
|
||||
fun hasV2KeyPair(context: Context): Boolean {
|
||||
return (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_SECRET_KEY) != null)
|
||||
}
|
||||
|
||||
fun getUserED25519KeyPair(context: Context): KeyPair? {
|
||||
val hexEncodedED25519PublicKey = IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_PUBLIC_KEY) ?: return null
|
||||
val hexEncodedED25519SecretKey = IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_SECRET_KEY) ?: return null
|
||||
val ed25519PublicKey = Key.fromBase64String(hexEncodedED25519PublicKey)
|
||||
val ed25519SecretKey = Key.fromBase64String(hexEncodedED25519SecretKey)
|
||||
return KeyPair(ed25519PublicKey, ed25519SecretKey)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user