Implement Session protocol

This commit is contained in:
Niels Andriesse
2020-12-14 13:06:46 +11:00
parent 6712572814
commit 48477a72ba
5 changed files with 121 additions and 1 deletions

View File

@@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
import org.thoughtcrime.securesms.jobs.TypingSendJob; import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.logging.Log; 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.SessionResetImplementation;
import org.thoughtcrime.securesms.loki.protocol.shelved.MultiDeviceOpenGroupUpdateJob; import org.thoughtcrime.securesms.loki.protocol.shelved.MultiDeviceOpenGroupUpdateJob;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
@@ -157,6 +158,7 @@ public class SignalCommunicationModule {
DatabaseFactory.getLokiThreadDatabase(context), DatabaseFactory.getLokiThreadDatabase(context),
DatabaseFactory.getLokiMessageDatabase(context), DatabaseFactory.getLokiMessageDatabase(context),
DatabaseFactory.getLokiPreKeyBundleDatabase(context), DatabaseFactory.getLokiPreKeyBundleDatabase(context),
new SessionProtocolImpl(context),
new SessionResetImplementation(context), new SessionResetImplementation(context),
DatabaseFactory.getLokiUserDatabase(context), DatabaseFactory.getLokiUserDatabase(context),
DatabaseFactory.getGroupDatabase(context), DatabaseFactory.getGroupDatabase(context),

View File

@@ -65,6 +65,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.activities.HomeActivity; 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.LokiMessageDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol; import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
@@ -259,7 +260,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context); SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context); SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(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); SignalServiceContent content = cipher.decrypt(envelope);

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

View File

@@ -3,13 +3,18 @@ package org.thoughtcrime.securesms.loki.database
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.util.Log 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.Database
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.utilities.* import org.thoughtcrime.securesms.loki.utilities.*
import org.thoughtcrime.securesms.util.TextSecurePreferences 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.api.Snode
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink
import org.whispersystems.signalservice.loki.utilities.toHexString
import java.util.* import java.util.*
class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiAPIDatabaseProtocol { class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiAPIDatabaseProtocol {

View File

@@ -1,8 +1,10 @@
package org.thoughtcrime.securesms.loki.utilities package org.thoughtcrime.securesms.loki.utilities
import android.content.Context import android.content.Context
import android.util.Log
import com.goterl.lazycode.lazysodium.LazySodiumAndroid import com.goterl.lazycode.lazysodium.LazySodiumAndroid
import com.goterl.lazycode.lazysodium.SodiumAndroid import com.goterl.lazycode.lazysodium.SodiumAndroid
import com.goterl.lazycode.lazysodium.utils.Key
import com.goterl.lazycode.lazysodium.utils.KeyPair import com.goterl.lazycode.lazysodium.utils.KeyPair
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.util.Base64 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.DjbECPrivateKey
import org.whispersystems.libsignal.ecc.DjbECPublicKey import org.whispersystems.libsignal.ecc.DjbECPublicKey
import org.whispersystems.libsignal.ecc.ECKeyPair import org.whispersystems.libsignal.ecc.ECKeyPair
import org.whispersystems.signalservice.loki.utilities.toHexString
object KeyPairUtilities { object KeyPairUtilities {
@@ -49,4 +52,12 @@ object KeyPairUtilities {
fun hasV2KeyPair(context: Context): Boolean { fun hasV2KeyPair(context: Context): Boolean {
return (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_SECRET_KEY) != null) 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)
}
} }