mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-19 19:08:26 +00:00
decryption and encrytion
This commit is contained in:
parent
bfb16c581a
commit
6cc20b81bd
@ -1,4 +1,56 @@
|
||||
package org.session.messaging.sending_receiving
|
||||
package org.session.libsession.messaging.sending_receiving
|
||||
|
||||
import org.session.libsession.messaging.Configuration
|
||||
import org.session.libsignal.service.api.push.SignalServiceAddress
|
||||
import org.session.libsignal.service.loki.crypto.LokiServiceCipher
|
||||
import org.session.libsession.messaging.sending_receiving.MessageReceiver.Error
|
||||
import org.session.libsession.utilities.AESGCM
|
||||
import org.whispersystems.curve25519.Curve25519
|
||||
import org.session.libsignal.libsignal.loki.ClosedGroupCiphertextMessage
|
||||
import org.session.libsignal.libsignal.util.Pair
|
||||
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysImplementation
|
||||
import org.session.libsignal.service.loki.utilities.toHexString
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
object MessageReceiverDecryption {
|
||||
|
||||
internal fun decryptWithSignalProtocol(envelope: SignalServiceEnvelope): Pair<ByteArray, String> {
|
||||
val storage = Configuration.shared.signalStorage
|
||||
val certificateValidator = Configuration.shared.certificateValidator
|
||||
val data = envelope.content
|
||||
if (data.count() == 0) { throw Error.NoData }
|
||||
val userPublicKey = Configuration.shared.storage.getUserPublicKey() ?: throw Error.NoUserPublicKey
|
||||
val cipher = LokiServiceCipher(SignalServiceAddress(userPublicKey), storage, sskDatabase, Configuration.shared.sessionResetImp, certificateValidator)
|
||||
val result = cipher.decrypt(envelope)
|
||||
}
|
||||
|
||||
internal fun decryptWithSharedSenderKeys(envelope: SignalServiceEnvelope): Pair<ByteArray, String> {
|
||||
// 1. ) Check preconditions
|
||||
val groupPublicKey = envelope.source
|
||||
if (!Configuration.shared.storage.isClosedGroup(groupPublicKey)) { throw Error.InvalidGroupPublicKey }
|
||||
val data = envelope.content
|
||||
if (data.count() == 0) { throw Error.NoData }
|
||||
val groupPrivateKey = Configuration.shared.storage.getClosedGroupPrivateKey(groupPublicKey) ?: throw Error.NoGroupPrivateKey
|
||||
// 2. ) Parse the wrapper
|
||||
val wrapper = SignalServiceProtos.ClosedGroupCiphertextMessageWrapper.parseFrom(data)
|
||||
val ivAndCiphertext = wrapper.ciphertext.toByteArray()
|
||||
val ephemeralPublicKey = wrapper.ephemeralPublicKey.toByteArray()
|
||||
// 3. ) Decrypt the data inside
|
||||
val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(ephemeralPublicKey, groupPrivateKey.serialize())
|
||||
val mac = Mac.getInstance("HmacSHA256")
|
||||
mac.init(SecretKeySpec("LOKI".toByteArray(), "HmacSHA256"))
|
||||
val symmetricKey = mac.doFinal(ephemeralSharedSecret)
|
||||
val closedGroupCiphertextMessageAsData = AESGCM.decrypt(ivAndCiphertext, symmetricKey)
|
||||
// 4. ) Parse the closed group ciphertext message
|
||||
val closedGroupCiphertextMessage = ClosedGroupCiphertextMessage.from(closedGroupCiphertextMessageAsData) ?: throw Error.ParsingFailed
|
||||
val senderPublicKey = closedGroupCiphertextMessage.senderPublicKey.toHexString()
|
||||
if (senderPublicKey == Configuration.shared.storage.getUserPublicKey()) { throw Error.SelfSend }
|
||||
// 5. ) Use the info inside the closed group ciphertext message to decrypt the actual message content
|
||||
val plaintext = SharedSenderKeysImplementation.shared.decrypt(closedGroupCiphertextMessage.ivAndCiphertext, groupPublicKey, senderPublicKey, closedGroupCiphertextMessage.keyIndex)
|
||||
// 6. ) Return
|
||||
return Pair(plaintext, senderPublicKey)
|
||||
}
|
||||
}
|
@ -1,4 +1,198 @@
|
||||
package org.session.messaging.sending_receiving
|
||||
package org.session.libsession.messaging.sending_receiving
|
||||
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.deferred
|
||||
|
||||
import org.session.libsession.messaging.Configuration
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.messages.Destination
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsession.messaging.jobs.NotifyPNServerJob
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupAPI
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupMessage
|
||||
import org.session.libsession.messaging.utilities.MessageWrapper
|
||||
import org.session.libsession.snode.RawResponsePromise
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.snode.SnodeMessage
|
||||
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import org.session.libsignal.service.internal.util.Base64
|
||||
import org.session.libsignal.service.loki.api.crypto.ProofOfWork
|
||||
|
||||
|
||||
object MessageSender {
|
||||
|
||||
// Error
|
||||
internal sealed class Error(val description: String) : Exception() {
|
||||
object InvalidMessage : Error("Invalid message.")
|
||||
object ProtoConversionFailed : Error("Couldn't convert message to proto.")
|
||||
object ProofOfWorkCalculationFailed : Error("Proof of work calculation failed.")
|
||||
object NoUserPublicKey : Error("Couldn't find user key pair.")
|
||||
|
||||
// Closed groups
|
||||
object NoThread : Error("Couldn't find a thread associated with the given group public key.")
|
||||
object NoPrivateKey : Error("Couldn't find a private key associated with the given group public key.")
|
||||
object InvalidClosedGroupUpdate : Error("Invalid group update.")
|
||||
|
||||
internal val isRetryable: Boolean = when (this) {
|
||||
is InvalidMessage -> false
|
||||
is ProtoConversionFailed -> false
|
||||
is ProofOfWorkCalculationFailed -> false
|
||||
is InvalidClosedGroupUpdate -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience
|
||||
fun send(message: Message, destination: Destination): Promise<Unit, Exception> {
|
||||
if (destination is Destination.OpenGroup) {
|
||||
return sendToOpenGroupDestination(destination, message)
|
||||
}
|
||||
return sendToSnodeDestination(destination, message)
|
||||
}
|
||||
|
||||
// One-on-One Chats & Closed Groups
|
||||
fun sendToSnodeDestination(destination: Destination, message: Message): Promise<Unit, Exception> {
|
||||
val deferred = deferred<Unit, Exception>()
|
||||
val promise = deferred.promise
|
||||
val storage = Configuration.shared.storage
|
||||
val preconditionFailure = Exception("Destination should not be open groups!")
|
||||
var snodeMessage: SnodeMessage? = null
|
||||
message.sentTimestamp ?: run { message.sentTimestamp = System.currentTimeMillis() } /* Visible messages will already have their sent timestamp set */
|
||||
message.sender = storage.getUserPublicKey()
|
||||
try {
|
||||
when (destination) {
|
||||
is Destination.Contact -> message.recipient = destination.publicKey
|
||||
is Destination.ClosedGroup -> message.recipient = destination.groupPublicKey
|
||||
is Destination.OpenGroup -> throw preconditionFailure
|
||||
}
|
||||
// Validate the message
|
||||
message.isValid ?: throw Error.InvalidMessage
|
||||
// Convert it to protobuf
|
||||
val proto = message.toProto() ?: throw Error.ProtoConversionFailed
|
||||
// Serialize the protobuf
|
||||
val plaintext = proto.toByteArray()
|
||||
// Encrypt the serialized protobuf
|
||||
val ciphertext: ByteArray
|
||||
when (destination) {
|
||||
is Destination.Contact -> ciphertext = MessageSenderEncryption.encryptWithSignalProtocol(plaintext, message, destination.publicKey)
|
||||
is Destination.ClosedGroup -> ciphertext = MessageSenderEncryption.encryptWithSharedSenderKeys(plaintext, destination.groupPublicKey)
|
||||
is Destination.OpenGroup -> throw preconditionFailure
|
||||
}
|
||||
// Wrap the result
|
||||
val kind: SignalServiceProtos.Envelope.Type
|
||||
val senderPublicKey: String
|
||||
when (destination) {
|
||||
is Destination.Contact -> {
|
||||
kind = SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER
|
||||
senderPublicKey = ""
|
||||
}
|
||||
is Destination.ClosedGroup -> {
|
||||
kind = SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT
|
||||
senderPublicKey = destination.groupPublicKey
|
||||
}
|
||||
is Destination.OpenGroup -> throw preconditionFailure
|
||||
}
|
||||
val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext)
|
||||
// Calculate proof of work
|
||||
val recipient = message.recipient!!
|
||||
val base64EncodedData = Base64.encodeBytes(wrappedMessage)
|
||||
val timestamp = System.currentTimeMillis()
|
||||
val nonce = ProofOfWork.calculate(base64EncodedData, recipient, timestamp, message.ttl.toInt()) ?: throw Error.ProofOfWorkCalculationFailed
|
||||
// Send the result
|
||||
snodeMessage = SnodeMessage(recipient, base64EncodedData, message.ttl, timestamp, nonce)
|
||||
SnodeAPI.sendMessage(snodeMessage).success { promises: Set<RawResponsePromise> ->
|
||||
var isSuccess = false
|
||||
val promiseCount = promises.size
|
||||
var errorCount = 0
|
||||
promises.forEach { promise: RawResponsePromise ->
|
||||
promise.success {
|
||||
if (isSuccess) { return@success } // Succeed as soon as the first promise succeeds
|
||||
isSuccess = true
|
||||
deferred.resolve(Unit)
|
||||
}
|
||||
promise.fail {
|
||||
errorCount += 1
|
||||
if (errorCount != promiseCount) { return@fail } // Only error out if all promises failed
|
||||
deferred.reject(it)
|
||||
}
|
||||
}
|
||||
}.fail {
|
||||
Log.d("Loki", "Couldn't send message due to error: $it.")
|
||||
deferred.reject(it)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
deferred.reject(exception)
|
||||
}
|
||||
// Handle completion
|
||||
promise.success {
|
||||
handleSuccessfulMessageSend(message)
|
||||
if (message is VisibleMessage && snodeMessage != null) {
|
||||
val notifyPNServerJob = NotifyPNServerJob(snodeMessage)
|
||||
JobQueue.shared.add(notifyPNServerJob)
|
||||
}
|
||||
}
|
||||
promise.fail {
|
||||
handleFailedMessageSend(message, it)
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
// Open Groups
|
||||
fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise<Unit, Exception> {
|
||||
val deferred = deferred<Unit, Exception>()
|
||||
val promise = deferred.promise
|
||||
val storage = Configuration.shared.storage
|
||||
val preconditionFailure = Exception("Destination should not be contacts or closed groups!")
|
||||
message.sentTimestamp = System.currentTimeMillis()
|
||||
message.sender = storage.getUserPublicKey()
|
||||
try {
|
||||
val server: String
|
||||
val channel: Long
|
||||
when (destination) {
|
||||
is Destination.Contact -> throw preconditionFailure
|
||||
is Destination.ClosedGroup -> throw preconditionFailure
|
||||
is Destination.OpenGroup -> {
|
||||
message.recipient = "${destination.server}.${destination.channel}"
|
||||
server = destination.server
|
||||
channel = destination.channel
|
||||
}
|
||||
}
|
||||
// Validate the message
|
||||
if (message !is VisibleMessage || !message.isValid) {
|
||||
throw Error.InvalidMessage
|
||||
}
|
||||
// Convert the message to an open group message
|
||||
val openGroupMessage = OpenGroupMessage.from(message, server) ?: throw Error.InvalidMessage
|
||||
// Send the result
|
||||
OpenGroupAPI.sendMessage(openGroupMessage, channel, server).success {
|
||||
message.openGroupServerMessageID = it.serverID
|
||||
deferred.resolve(Unit)
|
||||
}.fail {
|
||||
deferred.reject(it)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
deferred.reject(exception)
|
||||
}
|
||||
// Handle completion
|
||||
promise.success {
|
||||
handleSuccessfulMessageSend(message)
|
||||
}
|
||||
promise.fail {
|
||||
handleFailedMessageSend(message, it)
|
||||
}
|
||||
return deferred.promise
|
||||
}
|
||||
|
||||
// Result Handling
|
||||
fun handleSuccessfulMessageSend(message: Message) {
|
||||
|
||||
}
|
||||
|
||||
fun handleFailedMessageSend(message: Message, error: Exception) {
|
||||
|
||||
}
|
||||
}
|
@ -1,4 +1,49 @@
|
||||
package org.session.messaging.sending_receiving
|
||||
package org.session.libsession.messaging.sending_receiving
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
import org.session.libsession.messaging.Configuration
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender.Error
|
||||
import org.session.libsession.messaging.utilities.UnidentifiedAccessUtil
|
||||
import org.session.libsession.utilities.AESGCM
|
||||
import org.session.libsignal.libsignal.SignalProtocolAddress
|
||||
import org.session.libsignal.libsignal.loki.ClosedGroupCiphertextMessage
|
||||
import org.session.libsignal.libsignal.util.Hex
|
||||
import org.session.libsignal.service.api.crypto.SignalServiceCipher
|
||||
import org.session.libsignal.service.api.push.SignalServiceAddress
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import org.session.libsignal.service.internal.util.Base64
|
||||
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysImplementation
|
||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||
|
||||
object MessageSenderEncryption {
|
||||
|
||||
internal fun encryptWithSignalProtocol(plaintext: ByteArray, message: Message, recipientPublicKey: String): ByteArray{
|
||||
val storage = Configuration.shared.signalStorage
|
||||
val sskDatabase = Configuration.shared.sskDatabase
|
||||
val sessionResetImp = Configuration.shared.sessionResetImp
|
||||
val localAddress = SignalServiceAddress(recipientPublicKey)
|
||||
val certificateValidator = Configuration.shared.certificateValidator
|
||||
val cipher = SignalServiceCipher(localAddress, storage, sskDatabase, sessionResetImp, certificateValidator)
|
||||
val signalProtocolAddress = SignalProtocolAddress(recipientPublicKey, 1)
|
||||
val unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient)
|
||||
val encryptedMessage = cipher.encrypt(signalProtocolAddress, unidentifiedAccess,plaintext)
|
||||
return Base64.decode(encryptedMessage.content)
|
||||
}
|
||||
|
||||
internal fun encryptWithSharedSenderKeys(plaintext: ByteArray, groupPublicKey: String): ByteArray {
|
||||
// 1. ) Encrypt the data with the user's sender key
|
||||
val userPublicKey = Configuration.shared.storage.getUserPublicKey() ?: throw Error.NoUserPublicKey
|
||||
val ciphertextAndKeyIndex = SharedSenderKeysImplementation.shared.encrypt(plaintext, groupPublicKey, userPublicKey)
|
||||
val ivAndCiphertext = ciphertextAndKeyIndex.first
|
||||
val keyIndex = ciphertextAndKeyIndex.second
|
||||
val encryptedMessage = ClosedGroupCiphertextMessage(ivAndCiphertext, Hex.fromStringCondensed(userPublicKey), keyIndex);
|
||||
// 2. ) Encrypt the result for the group's public key to hide the sender public key and key index
|
||||
val intermediate = AESGCM.encrypt(encryptedMessage.serialize(), groupPublicKey.removing05PrefixIfNeeded())
|
||||
// 3. ) Wrap the result
|
||||
return SignalServiceProtos.ClosedGroupCiphertextMessageWrapper.newBuilder()
|
||||
.setCiphertext(ByteString.copyFrom(intermediate.ciphertext))
|
||||
.setEphemeralPublicKey(ByteString.copyFrom(intermediate.ephemeralPublicKey))
|
||||
.build().toByteArray()
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user