mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
Clean
This commit is contained in:
parent
69f05dabdf
commit
fdede1c656
@ -5,6 +5,7 @@ import android.text.TextUtils
|
||||
import com.google.protobuf.ByteString
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
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.threads.Address
|
||||
import org.session.libsession.messaging.utilities.DotNetAPI
|
||||
@ -26,7 +27,6 @@ import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider {
|
||||
|
||||
override fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? {
|
||||
@ -104,6 +104,10 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
|
||||
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) {
|
||||
val database = DatabaseFactory.getAttachmentDatabase(context)
|
||||
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return
|
||||
|
@ -30,10 +30,10 @@ import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
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.sending_receiving.MessageSender
|
||||
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.utilities.ThreadUtils
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
@ -139,7 +139,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
val userDB = DatabaseFactory.getLokiUserDatabase(this)
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||
if (userPublicKey != null) {
|
||||
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB)
|
||||
MentionsManager.configureIfNeeded(userPublicKey, userDB)
|
||||
application.publicChatManager.startPollersIfNeeded()
|
||||
JobQueue.shared.resumePendingJobs()
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
|
||||
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() {
|
||||
private val broadcastReceivers = mutableListOf<BroadcastReceiver>()
|
||||
|
@ -7,7 +7,7 @@ import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
|
||||
import org.session.libsignal.libsignal.ecc.DjbECPublicKey
|
||||
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.utilities.PublicKeyValidation
|
||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||
|
@ -1,5 +1,6 @@
|
||||
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.threads.Address
|
||||
import org.session.libsession.messaging.utilities.DotNetAPI
|
||||
@ -37,4 +38,5 @@ interface MessageDataProvider {
|
||||
fun getAttachmentIDsFor(messageID: Long): List<Long>
|
||||
fun getLinkPreviewAttachmentIDFor(messageID: Long): Long?
|
||||
|
||||
fun getOpenGroup(threadID: Long): OpenGroup?
|
||||
}
|
@ -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
|
||||
|
||||
class MentionsManager(private val userPublicKey: String, private val threadDatabase: LokiThreadDatabaseProtocol,
|
||||
private val userDatabase: LokiUserDatabaseProtocol) {
|
||||
class MentionsManager(private val userPublicKey: String, private val userDatabase: LokiUserDatabaseProtocol) {
|
||||
var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys
|
||||
|
||||
companion object {
|
||||
|
||||
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; }
|
||||
shared = MentionsManager(userPublicKey, threadDatabase, userDatabase)
|
||||
shared = MentionsManager(userPublicKey, userDatabase)
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +31,7 @@ class MentionsManager(private val userPublicKey: String, private val threadDatab
|
||||
// Prepare
|
||||
val cache = userPublicKeyCache[threadID] ?: return listOf()
|
||||
// Gather candidates
|
||||
val publicChat = threadDatabase.getPublicChat(threadID)
|
||||
val publicChat = MessagingModuleConfiguration.shared.messageDataProvider.getOpenGroup(threadID)
|
||||
var candidates: List<Mention> = cache.mapNotNull { publicKey ->
|
||||
val displayName: String?
|
||||
if (publicChat != null) {
|
@ -1,6 +1,5 @@
|
||||
package org.session.libsession.messaging.open_groups
|
||||
|
||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat
|
||||
import org.session.libsignal.utilities.JsonUtil
|
||||
|
||||
data class OpenGroup(
|
||||
@ -14,9 +13,6 @@ data class OpenGroup(
|
||||
|
||||
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 {
|
||||
return "$server.$channel"
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import org.session.libsession.messaging.jobs.MessageReceiveJob
|
||||
import org.session.libsession.messaging.utilities.MessageWrapper
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
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.logging.Log
|
||||
import java.security.SecureRandom
|
||||
|
@ -11,7 +11,7 @@ import org.session.libsession.utilities.AESGCM
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsignal.utilities.Base64
|
||||
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.libsession.utilities.AESGCM.EncryptionResult
|
||||
import org.session.libsignal.utilities.ThreadUtils
|
||||
@ -79,7 +79,7 @@ object OnionRequestAPI {
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import nl.komponents.kovenant.*
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import nl.komponents.kovenant.functional.map
|
||||
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.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.service.loki.utilities.Broadcaster
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.session.libsession.snode
|
||||
|
||||
import org.session.libsignal.service.loki.Snode
|
||||
|
||||
interface SnodeStorageProtocol {
|
||||
|
||||
fun getSnodePool(): Set<Snode>
|
||||
|
@ -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.push.SignalServiceAddress;
|
||||
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.List;
|
||||
@ -247,7 +246,7 @@ public class SignalServiceDataMessage {
|
||||
}
|
||||
|
||||
public int getTTL() {
|
||||
return TTLUtilities.getTTL(TTLUtilities.MessageType.Regular);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
@ -1,8 +1,6 @@
|
||||
package org.session.libsignal.service.api.messages;
|
||||
|
||||
|
||||
import org.session.libsignal.service.loki.utilities.TTLUtilities;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SignalServiceReceiptMessage {
|
||||
@ -41,5 +39,5 @@ public class SignalServiceReceiptMessage {
|
||||
return type == Type.READ;
|
||||
}
|
||||
|
||||
public int getTTL() { return TTLUtilities.getTTL(TTLUtilities.MessageType.Receipt); }
|
||||
public int getTTL() { return 0; }
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package org.session.libsignal.service.api.messages;
|
||||
|
||||
import org.session.libsignal.service.loki.utilities.TTLUtilities;
|
||||
|
||||
public class SignalServiceTypingMessage {
|
||||
|
||||
public enum Action {
|
||||
@ -32,5 +30,5 @@ public class SignalServiceTypingMessage {
|
||||
return action == Action.STOPPED;
|
||||
}
|
||||
|
||||
public int getTTL() { return TTLUtilities.getTTL(TTLUtilities.MessageType.TypingIndicator); }
|
||||
public int getTTL() { return 0; }
|
||||
}
|
||||
|
@ -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?) {
|
||||
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"),
|
||||
GetMessages("retrieve"),
|
||||
SendMessage("store")
|
@ -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
|
||||
}
|
@ -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" }
|
||||
}
|
@ -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 )
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package org.session.libsignal.service.loki.database
|
||||
|
||||
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.*
|
||||
|
||||
interface LokiAPIDatabaseProtocol {
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user