This commit is contained in:
Niels Andriesse 2021-04-26 11:39:23 +10:00
parent 69f05dabdf
commit fdede1c656
25 changed files with 31 additions and 484 deletions

View File

@ -5,6 +5,7 @@ import android.text.TextUtils
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.MessageDataProvider
import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.sending_receiving.attachments.* import org.session.libsession.messaging.sending_receiving.attachments.*
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.utilities.DotNetAPI import org.session.libsession.messaging.utilities.DotNetAPI
@ -26,7 +27,6 @@ import org.thoughtcrime.securesms.util.MediaUtil
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider { class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider {
override fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? { override fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? {
@ -104,6 +104,10 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp) return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp)
} }
override fun getOpenGroup(threadID: Long): OpenGroup? {
return null // TODO: Implement
}
override fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) { override fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) {
val database = DatabaseFactory.getAttachmentDatabase(context) val database = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return

View File

@ -30,10 +30,10 @@ import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.mentions.MentionsManager
import org.session.libsession.messaging.open_groups.OpenGroupAPI import org.session.libsession.messaging.open_groups.OpenGroupAPI
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.utilities.* import org.session.libsession.utilities.*
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.ThreadUtils
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
@ -139,7 +139,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
val userDB = DatabaseFactory.getLokiUserDatabase(this) val userDB = DatabaseFactory.getLokiUserDatabase(this)
val userPublicKey = TextSecurePreferences.getLocalNumber(this) val userPublicKey = TextSecurePreferences.getLocalNumber(this)
if (userPublicKey != null) { if (userPublicKey != null) {
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB) MentionsManager.configureIfNeeded(userPublicKey, userDB)
application.publicChatManager.startPollersIfNeeded() application.publicChatManager.startPollersIfNeeded()
JobQueue.shared.resumePendingJobs() JobQueue.shared.resumePendingJobs()
} }

View File

@ -24,7 +24,7 @@ import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.loki.utilities.* import org.thoughtcrime.securesms.loki.utilities.*
import org.thoughtcrime.securesms.loki.views.GlowViewUtilities import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
import org.thoughtcrime.securesms.loki.views.PathDotView import org.thoughtcrime.securesms.loki.views.PathDotView
import org.session.libsignal.service.loki.api.Snode import org.session.libsignal.service.loki.Snode
class PathActivity : PassphraseRequiredActionBarActivity() { class PathActivity : PassphraseRequiredActionBarActivity() {
private val broadcastReceivers = mutableListOf<BroadcastReceiver>() private val broadcastReceivers = mutableListOf<BroadcastReceiver>()

View File

@ -7,7 +7,7 @@ import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
import org.session.libsignal.libsignal.ecc.DjbECPublicKey import org.session.libsignal.libsignal.ecc.DjbECPublicKey
import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.service.loki.api.Snode import org.session.libsignal.service.loki.Snode
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.utilities.PublicKeyValidation import org.session.libsignal.service.loki.utilities.PublicKeyValidation
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded

View File

@ -1,5 +1,6 @@
package org.session.libsession.database package org.session.libsession.database
import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.sending_receiving.attachments.* import org.session.libsession.messaging.sending_receiving.attachments.*
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.utilities.DotNetAPI import org.session.libsession.messaging.utilities.DotNetAPI
@ -37,4 +38,5 @@ interface MessageDataProvider {
fun getAttachmentIDsFor(messageID: Long): List<Long> fun getAttachmentIDsFor(messageID: Long): List<Long>
fun getLinkPreviewAttachmentIDFor(messageID: Long): Long? fun getLinkPreviewAttachmentIDFor(messageID: Long): Long?
fun getOpenGroup(threadID: Long): OpenGroup?
} }

View File

@ -1,19 +1,20 @@
package org.session.libsignal.service.loki.utilities.mentions package org.session.libsession.messaging.mentions
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsignal.service.loki.utilities.mentions.Mention
import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol
class MentionsManager(private val userPublicKey: String, private val threadDatabase: LokiThreadDatabaseProtocol, class MentionsManager(private val userPublicKey: String, private val userDatabase: LokiUserDatabaseProtocol) {
private val userDatabase: LokiUserDatabaseProtocol) {
var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys
companion object { companion object {
public lateinit var shared: MentionsManager public lateinit var shared: MentionsManager
public fun configureIfNeeded(userPublicKey: String, threadDatabase: LokiThreadDatabaseProtocol, userDatabase: LokiUserDatabaseProtocol) { public fun configureIfNeeded(userPublicKey: String, userDatabase: LokiUserDatabaseProtocol) {
if (::shared.isInitialized) { return; } if (::shared.isInitialized) { return; }
shared = MentionsManager(userPublicKey, threadDatabase, userDatabase) shared = MentionsManager(userPublicKey, userDatabase)
} }
} }
@ -30,7 +31,7 @@ class MentionsManager(private val userPublicKey: String, private val threadDatab
// Prepare // Prepare
val cache = userPublicKeyCache[threadID] ?: return listOf() val cache = userPublicKeyCache[threadID] ?: return listOf()
// Gather candidates // Gather candidates
val publicChat = threadDatabase.getPublicChat(threadID) val publicChat = MessagingModuleConfiguration.shared.messageDataProvider.getOpenGroup(threadID)
var candidates: List<Mention> = cache.mapNotNull { publicKey -> var candidates: List<Mention> = cache.mapNotNull { publicKey ->
val displayName: String? val displayName: String?
if (publicChat != null) { if (publicChat != null) {

View File

@ -1,6 +1,5 @@
package org.session.libsession.messaging.open_groups package org.session.libsession.messaging.open_groups
import org.session.libsignal.service.loki.api.opengroups.PublicChat
import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.JsonUtil
data class OpenGroup( data class OpenGroup(
@ -14,9 +13,6 @@ data class OpenGroup(
companion object { companion object {
@JvmStatic fun from(publicChat: PublicChat): OpenGroup =
OpenGroup(publicChat.channel, publicChat.server, publicChat.displayName, publicChat.isDeletable)
@JvmStatic fun getId(channel: Long, server: String): String { @JvmStatic fun getId(channel: Long, server: String): String {
return "$server.$channel" return "$server.$channel"
} }

View File

@ -8,7 +8,7 @@ import org.session.libsession.messaging.jobs.MessageReceiveJob
import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeModule import org.session.libsession.snode.SnodeModule
import org.session.libsignal.service.loki.api.Snode import org.session.libsignal.service.loki.Snode
import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import java.security.SecureRandom import java.security.SecureRandom

View File

@ -11,7 +11,7 @@ import org.session.libsession.utilities.AESGCM
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.* import org.session.libsignal.utilities.*
import org.session.libsignal.service.loki.api.Snode import org.session.libsignal.service.loki.Snode
import org.session.libsignal.service.loki.api.utilities.* import org.session.libsignal.service.loki.api.utilities.*
import org.session.libsession.utilities.AESGCM.EncryptionResult import org.session.libsession.utilities.AESGCM.EncryptionResult
import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.ThreadUtils
@ -79,7 +79,7 @@ object OnionRequestAPI {
) )
internal sealed class Destination { internal sealed class Destination {
class Snode(val snode: org.session.libsignal.service.loki.api.Snode) : Destination() class Snode(val snode: org.session.libsignal.service.loki.Snode) : Destination()
class Server(val host: String, val target: String, val x25519PublicKey: String) : Destination() class Server(val host: String, val target: String, val x25519PublicKey: String) : Destination()
} }

View File

@ -7,7 +7,7 @@ import nl.komponents.kovenant.*
import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.functional.map
import org.session.libsession.snode.utilities.getRandomElement import org.session.libsession.snode.utilities.getRandomElement
import org.session.libsignal.service.loki.api.Snode import org.session.libsignal.service.loki.Snode
import org.session.libsignal.service.loki.api.utilities.HTTP import org.session.libsignal.service.loki.api.utilities.HTTP
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.utilities.Broadcaster import org.session.libsignal.service.loki.utilities.Broadcaster

View File

@ -1,5 +1,7 @@
package org.session.libsession.snode package org.session.libsession.snode
import org.session.libsignal.service.loki.Snode
interface SnodeStorageProtocol { interface SnodeStorageProtocol {
fun getSnodePool(): Set<Snode> fun getSnodePool(): Set<Snode>

View File

@ -10,7 +10,6 @@ import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.shared.SharedContact; import org.session.libsignal.service.api.messages.shared.SharedContact;
import org.session.libsignal.service.api.push.SignalServiceAddress; import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage.ClosedGroupControlMessage; import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage.ClosedGroupControlMessage;
import org.session.libsignal.service.loki.utilities.TTLUtilities;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -247,7 +246,7 @@ public class SignalServiceDataMessage {
} }
public int getTTL() { public int getTTL() {
return TTLUtilities.getTTL(TTLUtilities.MessageType.Regular); return 0;
} }
public static class Builder { public static class Builder {

View File

@ -1,8 +1,6 @@
package org.session.libsignal.service.api.messages; package org.session.libsignal.service.api.messages;
import org.session.libsignal.service.loki.utilities.TTLUtilities;
import java.util.List; import java.util.List;
public class SignalServiceReceiptMessage { public class SignalServiceReceiptMessage {
@ -41,5 +39,5 @@ public class SignalServiceReceiptMessage {
return type == Type.READ; return type == Type.READ;
} }
public int getTTL() { return TTLUtilities.getTTL(TTLUtilities.MessageType.Receipt); } public int getTTL() { return 0; }
} }

View File

@ -1,7 +1,5 @@
package org.session.libsignal.service.api.messages; package org.session.libsignal.service.api.messages;
import org.session.libsignal.service.loki.utilities.TTLUtilities;
public class SignalServiceTypingMessage { public class SignalServiceTypingMessage {
public enum Action { public enum Action {
@ -32,5 +30,5 @@ public class SignalServiceTypingMessage {
return action == Action.STOPPED; return action == Action.STOPPED;
} }
public int getTTL() { return TTLUtilities.getTTL(TTLUtilities.MessageType.TypingIndicator); } public int getTTL() { return 0; }
} }

View File

@ -1,9 +1,9 @@
package org.session.libsession.snode package org.session.libsignal.service.loki
class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) { class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) {
val ip: String get() = address.removePrefix("https://") val ip: String get() = address.removePrefix("https://")
internal enum class Method(val rawValue: String) { public enum class Method(val rawValue: String) {
GetSwarm("get_snodes_for_pubkey"), GetSwarm("get_snodes_for_pubkey"),
GetMessages("retrieve"), GetMessages("retrieve"),
SendMessage("store") SendMessage("store")

View File

@ -1,84 +0,0 @@
package org.session.libsignal.service.loki.api
import com.google.protobuf.ByteString
import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos.Envelope
import org.session.libsignal.utilities.Base64
import org.session.libsignal.service.internal.websocket.WebSocketProtos.WebSocketMessage
import org.session.libsignal.service.internal.websocket.WebSocketProtos.WebSocketRequestMessage
import java.security.SecureRandom
object MessageWrapper {
// region Types
sealed class Error(val description: String) : Exception() {
object FailedToWrapData : Error("Failed to wrap data.")
object FailedToWrapMessageInEnvelope : Error("Failed to wrap message in envelope.")
object FailedToWrapEnvelopeInWebSocketMessage : Error("Failed to wrap envelope in web socket message.")
object FailedToUnwrapData : Error("Failed to unwrap data.")
}
// endregion
// region Wrapping
/**
* Wraps `message` in a `SignalServiceProtos.Envelope` and then a `WebSocketProtos.WebSocketMessage` to match the desktop application.
*/
fun wrap(message: SignalMessageInfo): ByteArray {
try {
val envelope = createEnvelope(message)
val webSocketMessage = createWebSocketMessage(envelope)
return webSocketMessage.toByteArray()
} catch (e: Exception) {
throw if (e is Error) { e } else { Error.FailedToWrapData }
}
}
private fun createEnvelope(message: SignalMessageInfo): Envelope {
try {
val builder = Envelope.newBuilder()
builder.type = message.type
builder.timestamp = message.timestamp
builder.source = message.senderPublicKey
builder.sourceDevice = message.senderDeviceID
builder.content = ByteString.copyFrom(Base64.decode(message.content))
return builder.build()
} catch (e: Exception) {
Log.d("Loki", "Failed to wrap message in envelope: ${e.message}.")
throw Error.FailedToWrapMessageInEnvelope
}
}
private fun createWebSocketMessage(envelope: Envelope): WebSocketMessage {
try {
val requestBuilder = WebSocketRequestMessage.newBuilder()
requestBuilder.verb = "PUT"
requestBuilder.path = "/api/v1/message"
requestBuilder.id = SecureRandom.getInstance("SHA1PRNG").nextLong()
requestBuilder.body = envelope.toByteString()
val messageBuilder = WebSocketMessage.newBuilder()
messageBuilder.request = requestBuilder.build()
messageBuilder.type = WebSocketMessage.Type.REQUEST
return messageBuilder.build()
} catch (e: Exception) {
Log.d("Loki", "Failed to wrap envelope in web socket message: ${e.message}.")
throw Error.FailedToWrapEnvelopeInWebSocketMessage
}
}
// endregion
// region Unwrapping
/**
* `data` shouldn't be base 64 encoded.
*/
fun unwrap(data: ByteArray): Envelope {
try {
val webSocketMessage = WebSocketMessage.parseFrom(data)
val envelopeAsData = webSocketMessage.request.body
return Envelope.parseFrom(envelopeAsData)
} catch (e: Exception) {
Log.d("Loki", "Failed to unwrap data: ${e.message}.")
throw Error.FailedToUnwrapData
}
}
// endregion
}

View File

@ -1,34 +0,0 @@
package org.session.libsignal.service.loki.api
public class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) {
val ip: String get() = address.removePrefix("https://")
enum class Method(val rawValue: String) {
/**
* Only supported by snode targets.
*/
GetSwarm("get_snodes_for_pubkey"),
/**
* Only supported by snode targets.
*/
GetMessages("retrieve"),
SendMessage("store")
}
data class KeySet(val ed25519Key: String, val x25519Key: String)
override fun equals(other: Any?): Boolean {
return if (other is Snode) {
address == other.address && port == other.port
} else {
false
}
}
override fun hashCode(): Int {
return address.hashCode() xor port.hashCode()
}
override fun toString(): String { return "$address:$port" }
}

View File

@ -1,37 +0,0 @@
package org.session.libsignal.service.loki.api.opengroups
import org.session.libsignal.utilities.JsonUtil
public data class PublicChat(
public val channel: Long,
private val serverURL: String,
public val displayName: String,
public val isDeletable: Boolean
) {
public val server get() = serverURL.toLowerCase()
public val id get() = getId(channel, server)
companion object {
@JvmStatic fun getId(channel: Long, server: String): String {
return "$server.$channel"
}
@JvmStatic fun fromJSON(jsonAsString: String): PublicChat? {
try {
val json = JsonUtil.fromJson(jsonAsString)
val channel = json.get("channel").asLong()
val server = json.get("server").asText().toLowerCase()
val displayName = json.get("displayName").asText()
val isDeletable = json.get("isDeletable").asBoolean()
return PublicChat(channel, server, displayName, isDeletable)
} catch (e: Exception) {
return null
}
}
}
public fun toJSON(): Map<String, Any> {
return mapOf( "channel" to channel, "server" to server, "displayName" to displayName, "isDeletable" to isDeletable )
}
}

View File

@ -1,7 +0,0 @@
package org.session.libsignal.service.loki.api.opengroups
public data class PublicChatInfo (
public val displayName: String,
public val profilePictureURL: String,
public val memberCount: Int
)

View File

@ -1,178 +0,0 @@
package org.session.libsignal.service.loki.api.opengroups
import org.whispersystems.curve25519.Curve25519
import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.Hex
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
public data class PublicChatMessage(
public val serverID: Long?,
public val senderPublicKey: String,
public val displayName: String,
public val body: String,
public val timestamp: Long,
public val type: String,
public val quote: Quote?,
public val attachments: List<Attachment>,
public val profilePicture: ProfilePicture?,
public val signature: Signature?,
public val serverTimestamp: Long
) {
// region Settings
companion object {
private val curve = Curve25519.getInstance(Curve25519.BEST)
private val signatureVersion: Long = 1
private val attachmentType = "net.app.core.oembed"
}
// endregion
// region Types
public data class ProfilePicture(
public val profileKey: ByteArray,
public val url: String
)
public data class Quote(
public val quotedMessageTimestamp: Long,
public val quoteePublicKey: String,
public val quotedMessageBody: String,
public val quotedMessageServerID: Long? = null
)
public data class Signature(
public val data: ByteArray,
public val version: Long
)
public data class Attachment(
public val kind: Kind,
public val server: String,
public val serverID: Long,
public val contentType: String,
public val size: Int,
public val fileName: String,
public val flags: Int,
public val width: Int,
public val height: Int,
public val caption: String?,
public val url: String,
/**
Guaranteed to be non-`nil` if `kind` is `LinkPreview`.
*/
public val linkPreviewURL: String?,
/**
Guaranteed to be non-`nil` if `kind` is `LinkPreview`.
*/
public val linkPreviewTitle: String?
) {
public val dotNetAPIType = when {
contentType.startsWith("image") -> "photo"
contentType.startsWith("video") -> "video"
contentType.startsWith("audio") -> "audio"
else -> "other"
}
public enum class Kind(val rawValue: String) {
Attachment("attachment"), LinkPreview("preview")
}
}
// endregion
// region Initialization
constructor(hexEncodedPublicKey: String, displayName: String, body: String, timestamp: Long, type: String, quote: Quote?, attachments: List<Attachment>)
: this(null, hexEncodedPublicKey, displayName, body, timestamp, type, quote, attachments, null, null, 0)
// endregion
// region Crypto
internal fun sign(privateKey: ByteArray): PublicChatMessage? {
val data = getValidationData(signatureVersion)
if (data == null) {
Log.d("Loki", "Failed to sign public chat message.")
return null
}
try {
val signatureData = curve.calculateSignature(privateKey, data)
val signature = Signature(signatureData, signatureVersion)
return copy(signature = signature)
} catch (e: Exception) {
Log.d("Loki", "Failed to sign public chat message due to error: ${e.message}.")
return null
}
}
internal fun hasValidSignature(): Boolean {
if (signature == null) { return false }
val data = getValidationData(signature.version) ?: return false
val publicKey = Hex.fromStringCondensed(senderPublicKey.removing05PrefixIfNeeded())
try {
return curve.verifySignature(publicKey, data, signature.data)
} catch (e: Exception) {
Log.d("Loki", "Failed to verify public chat message due to error: ${e.message}.")
return false
}
}
// endregion
// region Parsing
internal fun toJSON(): Map<String, Any> {
val value = mutableMapOf<String, Any>( "timestamp" to timestamp )
if (quote != null) {
value["quote"] = mapOf( "id" to quote.quotedMessageTimestamp, "author" to quote.quoteePublicKey, "text" to quote.quotedMessageBody )
}
if (signature != null) {
value["sig"] = Hex.toStringCondensed(signature.data)
value["sigver"] = signature.version
}
val annotation = mapOf( "type" to type, "value" to value )
val annotations = mutableListOf( annotation )
attachments.forEach { attachment ->
val attachmentValue = mutableMapOf(
// Fields required by the .NET API
"version" to 1,
"type" to attachment.dotNetAPIType,
// Custom fields
"lokiType" to attachment.kind.rawValue,
"server" to attachment.server,
"id" to attachment.serverID,
"contentType" to attachment.contentType,
"size" to attachment.size,
"fileName" to attachment.fileName,
"flags" to attachment.flags,
"width" to attachment.width,
"height" to attachment.height,
"url" to attachment.url
)
if (attachment.caption != null) { attachmentValue["caption"] = attachment.caption }
if (attachment.linkPreviewURL != null) { attachmentValue["linkPreviewUrl"] = attachment.linkPreviewURL }
if (attachment.linkPreviewTitle != null) { attachmentValue["linkPreviewTitle"] = attachment.linkPreviewTitle }
val attachmentAnnotation = mapOf( "type" to attachmentType, "value" to attachmentValue )
annotations.add(attachmentAnnotation)
}
val result = mutableMapOf( "text" to body, "annotations" to annotations )
if (quote?.quotedMessageServerID != null) {
result["reply_to"] = quote.quotedMessageServerID
}
return result
}
// endregion
// region Convenience
private fun getValidationData(signatureVersion: Long): ByteArray? {
var string = "${body.trim()}$timestamp"
if (quote != null) {
string += "${quote.quotedMessageTimestamp}${quote.quoteePublicKey}${quote.quotedMessageBody.trim()}"
if (quote.quotedMessageServerID != null) {
string += "${quote.quotedMessageServerID}"
}
}
string += attachments.sortedBy { it.serverID }.map { it.serverID }.joinToString("")
string += "$signatureVersion"
try {
return string.toByteArray(Charsets.UTF_8)
} catch (exception: Exception) {
return null
}
}
// endregion
}

View File

@ -1,19 +0,0 @@
package org.session.libsignal.service.loki.api.utilities
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
internal object DecryptionUtilities {
/**
* Sync. Don't call from the main thread.
*/
internal fun decryptUsingAESGCM(ivAndCiphertext: ByteArray, symmetricKey: ByteArray): ByteArray {
val iv = ivAndCiphertext.sliceArray(0 until EncryptionUtilities.ivSize)
val ciphertext = ivAndCiphertext.sliceArray(EncryptionUtilities.ivSize until ivAndCiphertext.count())
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(symmetricKey, "AES"), GCMParameterSpec(EncryptionUtilities.gcmTagSize, iv))
return cipher.doFinal(ciphertext)
}
}

View File

@ -1,45 +0,0 @@
package org.session.libsignal.service.loki.api.utilities
import org.whispersystems.curve25519.Curve25519
import org.session.libsignal.libsignal.util.ByteUtil
import org.session.libsignal.utilities.Hex
import org.session.libsignal.service.internal.util.Util
import javax.crypto.Cipher
import javax.crypto.Mac
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
internal data class EncryptionResult(
internal val ciphertext: ByteArray,
internal val symmetricKey: ByteArray,
internal val ephemeralPublicKey: ByteArray
)
internal object EncryptionUtilities {
internal val gcmTagSize = 128
internal val ivSize = 12
/**
* Sync. Don't call from the main thread.
*/
internal fun encryptUsingAESGCM(plaintext: ByteArray, symmetricKey: ByteArray): ByteArray {
val iv = Util.getSecretBytes(ivSize)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(symmetricKey, "AES"), GCMParameterSpec(gcmTagSize, iv))
return ByteUtil.combine(iv, cipher.doFinal(plaintext))
}
/**
* Sync. Don't call from the main thread.
*/
internal fun encryptForX25519PublicKey(plaintext: ByteArray, hexEncodedX25519PublicKey: String): EncryptionResult {
val x25519PublicKey = Hex.fromStringCondensed(hexEncodedX25519PublicKey)
val ephemeralKeyPair = Curve25519.getInstance(Curve25519.BEST).generateKeyPair()
val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(x25519PublicKey, ephemeralKeyPair.privateKey)
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec("LOKI".toByteArray(), "HmacSHA256"))
val symmetricKey = mac.doFinal(ephemeralSharedSecret)
val ciphertext = encryptUsingAESGCM(plaintext, symmetricKey)
return EncryptionResult(ciphertext, symmetricKey, ephemeralKeyPair.publicKey)
}
}

View File

@ -1,7 +1,7 @@
package org.session.libsignal.service.loki.database package org.session.libsignal.service.loki.database
import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.service.loki.api.Snode import org.session.libsignal.service.loki.Snode
import java.util.* import java.util.*
interface LokiAPIDatabaseProtocol { interface LokiAPIDatabaseProtocol {

View File

@ -1,11 +0,0 @@
package org.session.libsignal.service.loki.database
import org.session.libsignal.service.loki.api.opengroups.PublicChat
interface LokiThreadDatabaseProtocol {
fun getThreadID(publicKey: String): Long
fun getPublicChat(threadID: Long): PublicChat?
fun setPublicChat(publicChat: PublicChat, threadID: Long)
fun removePublicChat(threadID: Long)
}

View File

@ -1,38 +0,0 @@
package org.session.libsignal.service.loki.utilities
public object TTLUtilities {
/**
* If a message type specifies an invalid TTL, this will be used.
*/
public val fallbackMessageTTL = 2 * 24 * 60 * 60 * 1000
public enum class MessageType {
// Unimportant control messages
Address, Call, TypingIndicator, Verified,
// Somewhat important control messages
DeviceLink,
// Important control messages
ClosedGroupUpdate, Ephemeral, SessionRequest, Receipt, Sync, DeviceUnlinkingRequest,
// Visible messages
Regular
}
@JvmStatic
public fun getTTL(messageType: MessageType): Int {
val minuteInMs = 60 * 1000
val hourInMs = 60 * minuteInMs
val dayInMs = 24 * hourInMs
return when (messageType) {
// Unimportant control messages
MessageType.Address, MessageType.Call, MessageType.TypingIndicator, MessageType.Verified -> 20 * 1000
// Somewhat important control messages
MessageType.DeviceLink -> 1 * hourInMs
// Important control messages
MessageType.ClosedGroupUpdate, MessageType.Ephemeral, MessageType.SessionRequest, MessageType.Receipt,
MessageType.Sync, MessageType.DeviceUnlinkingRequest -> 2 * dayInMs - 1 * hourInMs
// Visible messages
MessageType.Regular -> 2 * dayInMs
}
}
}