mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 10:05:15 +00:00
feat: finishing up OpenGroupAPIV2.kt calls
This commit is contained in:
parent
96e604d06b
commit
aea23a6fc1
@ -314,15 +314,19 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
}
|
||||
|
||||
override fun getLastDeletionServerId(room: String, server: String): Long? {
|
||||
TODO("Not yet implemented")
|
||||
return DatabaseFactory.getLokiAPIDatabase(context).getLastDeletionServerID(room, server)
|
||||
}
|
||||
|
||||
override fun setLastDeletionServerId(room: String, server: String, newValue: Long) {
|
||||
TODO("Not yet implemented")
|
||||
DatabaseFactory.getLokiAPIDatabase(context).setLastDeletionServerID(room, server, newValue)
|
||||
}
|
||||
|
||||
override fun removeLastDeletionServerId(room: String, server: String) {
|
||||
TODO("Not yet implemented")
|
||||
DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(room, server)
|
||||
}
|
||||
|
||||
override fun setUserCount(room: String, server: String, newValue: Long) {
|
||||
DatabaseFactory.getLokiAPIDatabase(context).setUserCount(room, server, newValue)
|
||||
}
|
||||
|
||||
override fun getLastDeletionServerID(group: Long, server: String): Long? {
|
||||
|
@ -316,7 +316,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
|
||||
fun removeLastMessageServerID(room: String, server:String) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val index = "$server.$channel"
|
||||
val index = "$server.$room"
|
||||
database.delete(lastMessageServerIDTable, "$lastMessageServerIDTableIndex = ?", wrap(index))
|
||||
}
|
||||
|
||||
@ -350,6 +350,12 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
database.insertOrUpdate(lastDeletionServerIDTable, row, "$lastDeletionServerIDTableIndex = ?", wrap(index))
|
||||
}
|
||||
|
||||
fun removeLastDeletionServerID(room: String, server: String) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val index = "$server.$room"
|
||||
database.delete(lastDeletionServerIDTable, "$lastDeletionServerID = ?", wrap(index))
|
||||
}
|
||||
|
||||
fun removeLastDeletionServerID(group: Long, server: String) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val index = "$server.$group"
|
||||
@ -379,7 +385,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
database.insertOrUpdate(userCountTable, row, "$publicChatID = ?", wrap(index))
|
||||
}
|
||||
|
||||
override fun setUserCount(room: String, server: String, newValue: Int) {
|
||||
override fun setUserCount(room: String, server: String, newValue: Long) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val index = "$server.$room"
|
||||
val row = wrap(mapOf( publicChatID to index, userCount to newValue.toString() ))
|
||||
|
@ -78,6 +78,7 @@ interface StorageProtocol {
|
||||
|
||||
fun updateTitle(groupID: String, newValue: String)
|
||||
fun updateProfilePicture(groupID: String, newValue: ByteArray)
|
||||
fun setUserCount(room: String, server: String, newValue: Long)
|
||||
|
||||
// Last Message Server ID
|
||||
fun getLastMessageServerId(room: String, server: String): Long?
|
||||
|
@ -1,19 +1,36 @@
|
||||
package org.session.libsession.messaging.opengroups
|
||||
|
||||
import nl.komponents.kovenant.Kovenant
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.RequestBody
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.Error
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsession.utilities.AESGCM
|
||||
import org.session.libsignal.service.loki.api.utilities.HTTP
|
||||
import org.session.libsignal.service.loki.api.utilities.HTTP.Verb.*
|
||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||
import org.session.libsignal.service.loki.utilities.toHexString
|
||||
import org.session.libsignal.utilities.Base64.*
|
||||
import org.session.libsignal.utilities.JsonUtil
|
||||
import org.session.libsignal.utilities.createContext
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.whispersystems.curve25519.Curve25519
|
||||
import java.util.*
|
||||
|
||||
object OpenGroupAPIV2 {
|
||||
|
||||
private val moderators: HashMap<String, HashMap<String, Set<String>>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs)
|
||||
private val moderators: HashMap<String, Set<String>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs)
|
||||
const val DEFAULT_SERVER = "https://sessionopengroup.com"
|
||||
const val DEFAULT_SERVER_PUBLIC_KEY = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b"
|
||||
|
||||
private val sharedContext = Kovenant.createContext()
|
||||
private val curve = Curve25519.getInstance(Curve25519.BEST)
|
||||
|
||||
sealed class Error : Exception() {
|
||||
object GENERIC : Error()
|
||||
object PARSING_FAILED : Error()
|
||||
@ -26,7 +43,7 @@ object OpenGroupAPIV2 {
|
||||
data class Info(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val imageID: String
|
||||
val imageID: String?
|
||||
)
|
||||
|
||||
data class Request(
|
||||
@ -34,30 +51,67 @@ object OpenGroupAPIV2 {
|
||||
val room: String?,
|
||||
val server: String,
|
||||
val endpoint: String,
|
||||
val queryParameters: Map<String, String>,
|
||||
val parameters: Any,
|
||||
val headers: Map<String, String>,
|
||||
val isAuthRequired: Boolean,
|
||||
val queryParameters: Map<String, String> = mapOf(),
|
||||
val parameters: Any? = null,
|
||||
val headers: Map<String, String> = mapOf(),
|
||||
val isAuthRequired: Boolean = true,
|
||||
// Always `true` under normal circumstances. You might want to disable
|
||||
// this when running over Lokinet.
|
||||
val useOnionRouting: Boolean
|
||||
val useOnionRouting: Boolean = true
|
||||
)
|
||||
|
||||
private fun send(request: Request): Promise<Any, Exception> {
|
||||
private fun createBody(parameters: Any): RequestBody {
|
||||
val parametersAsJSON = JsonUtil.toJson(parameters)
|
||||
return RequestBody.create(MediaType.get("application/json"), parametersAsJSON)
|
||||
}
|
||||
|
||||
private fun send(request: Request): Promise<Map<*,*>, Exception> {
|
||||
val parsed = HttpUrl.parse(request.server) ?: return Promise.ofFail(Error.INVALID_URL)
|
||||
val urlBuilder = HttpUrl.Builder()
|
||||
.scheme(parsed.scheme())
|
||||
.host(parsed.host())
|
||||
.addPathSegment(request.endpoint)
|
||||
|
||||
for ((key, value) in request.queryParameters) {
|
||||
urlBuilder.addQueryParameter(key, value)
|
||||
if (request.verb == GET) {
|
||||
for ((key, value) in request.queryParameters) {
|
||||
urlBuilder.addQueryParameter(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
fun execute(token: String?): Promise<Map<*, *>, Exception> {
|
||||
val requestBuilder = okhttp3.Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
if (request.isAuthRequired) {
|
||||
if (token.isNullOrEmpty()) throw IllegalStateException("No auth token for request")
|
||||
requestBuilder.addHeader("Authorization", token)
|
||||
}
|
||||
when (request.verb) {
|
||||
GET -> requestBuilder.get()
|
||||
PUT -> requestBuilder.put(createBody(request.parameters!!))
|
||||
POST -> requestBuilder.post(createBody(request.parameters!!))
|
||||
DELETE -> requestBuilder.delete(createBody(request.parameters!!))
|
||||
}
|
||||
|
||||
if (!request.room.isNullOrEmpty()) {
|
||||
requestBuilder.header("Room", request.room)
|
||||
}
|
||||
|
||||
if (request.useOnionRouting) {
|
||||
val publicKey = MessagingConfiguration.shared.storage.getOpenGroupPublicKey(request.server)
|
||||
?: return Promise.ofFail(Error.NO_PUBLIC_KEY)
|
||||
return OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey)
|
||||
.fail { e ->
|
||||
if (e is OnionRequestAPI.HTTPRequestFailedAtDestinationException
|
||||
&& e.statusCode == 401) {
|
||||
MessagingConfiguration.shared.storage.removeAuthToken(request.server)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests."))
|
||||
}
|
||||
}
|
||||
return if (request.isAuthRequired) {
|
||||
getAuthToken(request.room!!, request.server).bind(::execute)
|
||||
getAuthToken(request.room!!, request.server).bind(sharedContext) { execute(it) }
|
||||
} else {
|
||||
execute(null)
|
||||
}
|
||||
@ -69,7 +123,7 @@ object OpenGroupAPIV2 {
|
||||
Promise.of(it)
|
||||
} ?: run {
|
||||
requestNewAuthToken(room, server)
|
||||
.bind { claimAuthToken(it, room, server) }
|
||||
.bind(sharedContext) { claimAuthToken(it, room, server) }
|
||||
.success { authToken ->
|
||||
storage.setAuthToken(room, server, authToken)
|
||||
}
|
||||
@ -77,75 +131,204 @@ object OpenGroupAPIV2 {
|
||||
}
|
||||
|
||||
fun requestNewAuthToken(room: String, server: String): Promise<String, Exception> {
|
||||
val (publicKey, _) = MessagingConfiguration.shared.storage.getUserKeyPair()
|
||||
val (publicKey, privateKey) = MessagingConfiguration.shared.storage.getUserKeyPair()
|
||||
?: return Promise.ofFail(Error.GENERIC)
|
||||
val queryParameters = mutableMapOf("public_key" to publicKey)
|
||||
|
||||
val request = Request(GET, room, server, "auth_token_challenge", queryParameters, isAuthRequired = false, parameters = null)
|
||||
return send(request).map(sharedContext) { json ->
|
||||
val challenge = json["challenge"] as? Map<*,*> ?: throw Error.PARSING_FAILED
|
||||
val base64EncodedCiphertext = challenge["ciphertext"] as? String ?: throw Error.PARSING_FAILED
|
||||
val base64EncodedEphemeralPublicKey = challenge["ephemeral_public_key"] as? String ?: throw Error.PARSING_FAILED
|
||||
val ciphertext = decode(base64EncodedCiphertext)
|
||||
val ephemeralPublicKey = decode(base64EncodedEphemeralPublicKey)
|
||||
val symmetricKey = AESGCM.generateSymmetricKey(ephemeralPublicKey, privateKey)
|
||||
val tokenAsData = try {
|
||||
AESGCM.decrypt(ciphertext, symmetricKey)
|
||||
} catch (e: Exception) {
|
||||
throw Error.DECRYPTION_FAILED
|
||||
}
|
||||
tokenAsData.toHexString()
|
||||
}
|
||||
}
|
||||
|
||||
fun claimAuthToken(authToken: String, room: String, server: String): Promise<String, Exception> {
|
||||
TODO("implement")
|
||||
val parameters = mapOf("public_key" to MessagingConfiguration.shared.storage.getUserPublicKey()!!)
|
||||
val headers = mapOf("Authorization" to authToken)
|
||||
val request = Request(verb = POST, room = room, server = server, endpoint = "claim_auth_token",
|
||||
parameters = parameters, headers = headers, isAuthRequired = false)
|
||||
return send(request).map(sharedContext) { authToken }
|
||||
}
|
||||
|
||||
fun deleteAuthToken(room: String, server: String): Promise<Long, Exception> {
|
||||
TODO("implement")
|
||||
fun deleteAuthToken(room: String, server: String): Promise<Unit, Exception> {
|
||||
val request = Request(verb = DELETE, room = room, server = server, endpoint = "auth_token")
|
||||
return send(request).map(sharedContext) {
|
||||
MessagingConfiguration.shared.storage.removeAuthToken(room, server)
|
||||
}
|
||||
}
|
||||
|
||||
// region Sending
|
||||
fun upload(file: ByteArray, room: String, server: String): Promise<Long, Exception> {
|
||||
TODO("implement")
|
||||
val base64EncodedFile = encodeBytes(file)
|
||||
val parameters = mapOf("file" to base64EncodedFile)
|
||||
val request = Request(verb = POST, room = room, server = server, endpoint = "files", parameters = parameters)
|
||||
return send(request).map(sharedContext) { json ->
|
||||
json["result"] as? Long ?: throw Error.PARSING_FAILED
|
||||
}
|
||||
}
|
||||
|
||||
fun download(file: Long, room: String, server: String): Promise<ByteArray, Exception> {
|
||||
TODO("implement")
|
||||
val request = Request(verb = GET, room = room, server = server, endpoint = "files/$file")
|
||||
return send(request).map(sharedContext) { json ->
|
||||
val base64EncodedFile = json["result"] as? String ?: throw Error.PARSING_FAILED
|
||||
decode(base64EncodedFile) ?: throw Error.PARSING_FAILED
|
||||
}
|
||||
}
|
||||
|
||||
fun send(message: OpenGroupMessageV2, room: String, server: String): Promise<OpenGroupMessageV2, Exception> {
|
||||
TODO("implement")
|
||||
val signedMessage = message.sign() ?: return Promise.ofFail(Error.SIGNING_FAILED)
|
||||
val json = signedMessage.toJSON()
|
||||
val request = Request(verb = POST, room = room, server = server, endpoint = "messages", parameters = json)
|
||||
return send(request).map(sharedContext) {
|
||||
@Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map<String,String> ?: throw Error.PARSING_FAILED
|
||||
OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.PARSING_FAILED
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Messages
|
||||
fun getMessages(room: String, server: String): Promise<List<OpenGroupMessageV2>, Exception> {
|
||||
TODO("implement")
|
||||
}
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val queryParameters = mutableMapOf<String,String>()
|
||||
storage.getLastMessageServerId(room,server)?.let { lastId ->
|
||||
queryParameters += "from_server_id" to lastId.toString()
|
||||
}
|
||||
val request = Request(verb = GET, room = room, server = server, endpoint = "messages", queryParameters = queryParameters)
|
||||
return send(request).map(sharedContext) { jsonList ->
|
||||
@Suppress("UNCHECKED_CAST") val rawMessages = jsonList["messages"] as? List<Map<String,Any>> ?: throw Error.PARSING_FAILED
|
||||
val lastMessageServerId = storage.getLastMessageServerId(room, server) ?: 0
|
||||
|
||||
var currentMax = lastMessageServerId
|
||||
val messages = rawMessages.mapNotNull { json ->
|
||||
val message = OpenGroupMessageV2.fromJSON(json) ?: return@mapNotNull null
|
||||
if (message.serverID == null || message.sender.isNullOrEmpty()) return@mapNotNull null
|
||||
val sender = message.sender
|
||||
val data = decode(message.base64EncodedData)
|
||||
val signature = decode(message.base64EncodedSignature)
|
||||
val publicKey = sender.removing05PrefixIfNeeded().encodeToByteArray()
|
||||
val isValid = curve.verifySignature(publicKey, data, signature)
|
||||
if (!isValid) {
|
||||
Log.d("Loki", "Ignoring message with invalid signature")
|
||||
return@mapNotNull null
|
||||
}
|
||||
if (message.serverID > lastMessageServerId) {
|
||||
currentMax = message.serverID
|
||||
}
|
||||
message
|
||||
}
|
||||
storage.setLastMessageServerId(room,server,currentMax)
|
||||
messages
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Message Deletion
|
||||
fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> {
|
||||
TODO("implement")
|
||||
val request = Request(verb = DELETE, room = room, server = server, endpoint = "message/$serverID")
|
||||
return send(request).map(sharedContext) {
|
||||
Log.d("Loki", "Deleted server message")
|
||||
}
|
||||
}
|
||||
|
||||
fun getDeletedMessages(room: String, server: String): Promise<List<Long>, Exception> {
|
||||
TODO("implement")
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val queryParameters = mutableMapOf<String,String>()
|
||||
storage.getLastDeletionServerId(room, server)?.let { last ->
|
||||
queryParameters["from_server_id"] = last.toString()
|
||||
}
|
||||
val request = Request(verb = GET, room = room, server = server, endpoint = "deleted_messages", queryParameters = queryParameters)
|
||||
return send(request).map(sharedContext) { json ->
|
||||
@Suppress("UNCHECKED_CAST") val serverIDs = json["ids"] as? List<Long> ?: throw Error.PARSING_FAILED
|
||||
val lastMessageServerId = storage.getLastMessageServerId(room, server) ?: 0
|
||||
val serverID = serverIDs.maxOrNull() ?: 0
|
||||
if (serverID > lastMessageServerId) {
|
||||
storage.setLastDeletionServerId(room, server, serverID)
|
||||
}
|
||||
serverIDs
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Moderation
|
||||
fun getModerators(room: String, server: String): Promise<List<String>, Exception> {
|
||||
TODO("implement")
|
||||
val request = Request(verb = GET, room = room, server = server, endpoint = "moderators")
|
||||
return send(request).map(sharedContext) { json ->
|
||||
@Suppress("UNCHECKED_CAST") val moderatorsJson = json["moderators"] as? List<String> ?: throw Error.PARSING_FAILED
|
||||
val id = "$server.$room"
|
||||
moderators[id] = moderatorsJson.toMutableSet()
|
||||
moderatorsJson
|
||||
}
|
||||
}
|
||||
|
||||
fun ban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
||||
TODO("implement")
|
||||
val parameters = mapOf("public_key" to publicKey)
|
||||
val request = Request(verb = POST, room = room, server = server, endpoint = "block_list", parameters = parameters)
|
||||
return send(request).map(sharedContext) {
|
||||
Log.d("Loki", "Banned user $publicKey from $server.$room")
|
||||
}
|
||||
}
|
||||
|
||||
fun unban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
||||
TODO("implement")
|
||||
val request = Request(verb = DELETE, room = room, server = server, endpoint = "block_list/$publicKey")
|
||||
return send(request).map(sharedContext) {
|
||||
Log.d("Loki", "Unbanned user $publicKey from $server.$room")
|
||||
}
|
||||
}
|
||||
|
||||
fun isUserModerator(publicKey: String, room: String, server: String): Promise<Boolean, Exception> {
|
||||
TODO("implement")
|
||||
}
|
||||
fun isUserModerator(publicKey: String, room: String, server: String): Boolean = moderators["$server.$room"]?.contains(publicKey) ?: false
|
||||
// endregion
|
||||
|
||||
fun getDefaultRoomsIfNeeded() {
|
||||
TODO("implement")
|
||||
// region General
|
||||
fun getDefaultRoomsIfNeeded(): Promise<List<Info>, Exception> {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
storage.setOpenGroupPublicKey(DEFAULT_SERVER, DEFAULT_SERVER_PUBLIC_KEY)
|
||||
return getAllRooms(DEFAULT_SERVER)
|
||||
}
|
||||
|
||||
fun getInfo(room: String, server: String): Promise<Info, Exception> {
|
||||
TODO("implement")
|
||||
val request = Request(verb = GET, room = room, server = server, endpoint = "rooms/$room", isAuthRequired = false)
|
||||
return send(request).map(sharedContext) { json ->
|
||||
val rawRoom = json["room"] as? Map<*,*> ?: throw Error.PARSING_FAILED
|
||||
val id = rawRoom["id"] as? String ?: throw Error.PARSING_FAILED
|
||||
val name = rawRoom["name"] as? String ?: throw Error.PARSING_FAILED
|
||||
val imageID = rawRoom["image_id"] as? String
|
||||
Info(id = id, name = name, imageID = imageID)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllRooms(server: String): Promise<List<Info>, Exception> {
|
||||
TODO("implement")
|
||||
val request = Request(verb = GET, room = null, server = server, endpoint = "rooms", isAuthRequired = false)
|
||||
return send(request).map(sharedContext) { json ->
|
||||
val rawRooms = json["rooms"] as? Map<*,*> ?: throw Error.PARSING_FAILED
|
||||
rawRooms.mapNotNull {
|
||||
val roomJson = it as? Map<*, *> ?: return@mapNotNull null
|
||||
val id = roomJson["id"] as? String ?: return@mapNotNull null
|
||||
val name = roomJson["name"] as? String ?: return@mapNotNull null
|
||||
val imageId = roomJson["image_id"] as? String
|
||||
Info(id, name, imageId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getMemberCount(room: String, server: String): Promise<Long, Exception> {
|
||||
TODO("implement")
|
||||
val request = Request(verb = GET, room = room, server = server, endpoint = "member_count")
|
||||
return send(request).map(sharedContext) { json ->
|
||||
val memberCount = json["member_count"] as? Long ?: throw Error.PARSING_FAILED
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
storage.setUserCount(room, server, memberCount)
|
||||
memberCount
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,21 @@ data class OpenGroupMessageV2(
|
||||
|
||||
companion object {
|
||||
private val curve = Curve25519.getInstance(Curve25519.BEST)
|
||||
|
||||
fun fromJSON(json: Map<String, Any>): OpenGroupMessageV2? {
|
||||
val base64EncodedData = json["data"] as? String ?: return null
|
||||
val sentTimestamp = json["timestamp"] as? Long ?: return null
|
||||
val serverID = json["server_id"] as? Long
|
||||
val sender = json["public_key"] as? String
|
||||
val base64EncodedSignature = json["signature"] as? String
|
||||
return OpenGroupMessageV2(serverID = serverID,
|
||||
sender = sender,
|
||||
sentTimestamp = sentTimestamp,
|
||||
base64EncodedData = base64EncodedData,
|
||||
base64EncodedSignature = base64EncodedSignature
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun sign(): OpenGroupMessageV2? {
|
||||
@ -43,20 +58,4 @@ data class OpenGroupMessageV2(
|
||||
base64EncodedSignature?.let { jsonMap["signature"] = base64EncodedSignature }
|
||||
return jsonMap
|
||||
}
|
||||
|
||||
fun fromJSON(json: Map<String, Any>): OpenGroupMessageV2? {
|
||||
val base64EncodedData = json["data"] as? String ?: return null
|
||||
val sentTimestamp = json["timestamp"] as? Long ?: return null
|
||||
val serverID = json["server_id"] as? Long
|
||||
val sender = json["public_key"] as? String
|
||||
val base64EncodedSignature = json["signature"] as? String
|
||||
return OpenGroupMessageV2(serverID = serverID,
|
||||
sender = sender,
|
||||
sentTimestamp = sentTimestamp,
|
||||
base64EncodedData = base64EncodedData,
|
||||
base64EncodedSignature = base64EncodedSignature
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,14 +1,16 @@
|
||||
package org.session.libsession.utilities
|
||||
|
||||
import org.whispersystems.curve25519.Curve25519
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.session.libsignal.libsignal.util.ByteUtil
|
||||
import org.session.libsignal.service.internal.util.Util
|
||||
import org.session.libsignal.utilities.Hex
|
||||
import org.whispersystems.curve25519.Curve25519
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
@WorkerThread
|
||||
internal object AESGCM {
|
||||
|
||||
internal data class EncryptionResult(
|
||||
@ -31,6 +33,16 @@ internal object AESGCM {
|
||||
return cipher.doFinal(ciphertext)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync. Don't call from the main thread.
|
||||
*/
|
||||
internal fun generateSymmetricKey(x25519PublicKey: ByteArray, x25519PrivateKey: ByteArray): ByteArray {
|
||||
val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(x25519PublicKey, x25519PrivateKey)
|
||||
val mac = Mac.getInstance("HmacSHA256")
|
||||
mac.init(SecretKeySpec("LOKI".toByteArray(), "HmacSHA256"))
|
||||
return mac.doFinal(ephemeralSharedSecret)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync. Don't call from the main thread.
|
||||
*/
|
||||
@ -47,10 +59,7 @@ internal object AESGCM {
|
||||
internal fun encrypt(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 symmetricKey = generateSymmetricKey(x25519PublicKey, ephemeralKeyPair.privateKey)
|
||||
val ciphertext = encrypt(plaintext, symmetricKey)
|
||||
return EncryptionResult(ciphertext, symmetricKey, ephemeralKeyPair.publicKey)
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ interface LokiAPIDatabaseProtocol {
|
||||
fun setLastMessageServerID(room: String, server: String, newValue: Long)
|
||||
fun getLastDeletionServerID(room: String, server: String): Long?
|
||||
fun setLastDeletionServerID(room: String, server: String, newValue: Long)
|
||||
fun setUserCount(room: String, server: String, newValue: Int)
|
||||
fun setUserCount(room: String, server: String, newValue: Long)
|
||||
fun getSessionRequestSentTimestamp(publicKey: String): Long?
|
||||
fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long)
|
||||
fun getSessionRequestProcessedTimestamp(publicKey: String): Long?
|
||||
|
Loading…
Reference in New Issue
Block a user