Skip to content

Commit

Permalink
✨ Add signal protocol support (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
guiyanakuang authored Nov 21, 2023
1 parent d3c8d1d commit 264424d
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 1 deletion.
1 change: 1 addition & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ kotlin {
implementation("com.google.zxing:javase:3.5.2")
implementation("ch.qos.logback:logback-classic:1.4.11")
implementation("io.javalin:javalin:5.6.3")
implementation("org.whispersystems:signal-protocol-java:2.8.1")
}
commonMain.dependencies {
implementation(compose.runtime)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ fun ClipeveryCommon(dependencies: Dependencies) {
CompositionLocalProvider(
LocalClipeveryServer provides dependencies.clipServer,
LocalConfigManager provides dependencies.configManager,
LocalFilePersist provides dependencies.filePersist
LocalFilePersist provides dependencies.filePersist,
LocalSignalProtocol provides dependencies.signalProtocol,
) {
ClipeveryWithProvidedDependencies()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package com.clipevery

import androidx.compose.runtime.staticCompositionLocalOf
import com.clipevery.config.ConfigManager
import com.clipevery.encrypt.SignalProtocol
import com.clipevery.net.ClipServer
import com.clipevery.presist.FilePersist

abstract class Dependencies {
abstract val clipServer: ClipServer
abstract val configManager: ConfigManager
abstract val filePersist: FilePersist
abstract val signalProtocol: SignalProtocol
}

internal val LocalClipeveryServer = staticCompositionLocalOf<ClipServer> {
Expand All @@ -23,6 +25,10 @@ internal val LocalFilePersist = staticCompositionLocalOf<FilePersist> {
noLocalProvidedFor("FilePersist")
}

internal val LocalSignalProtocol = staticCompositionLocalOf<SignalProtocol> {
noLocalProvidedFor("SignalProtocol")
}

private fun noLocalProvidedFor(name: String): Nothing {
error("CompositionLocal $name not present")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.clipevery.encrypt

import org.whispersystems.libsignal.IdentityKeyPair
import org.whispersystems.libsignal.state.IdentityKeyStore
import org.whispersystems.libsignal.state.PreKeyRecord
import org.whispersystems.libsignal.state.PreKeyStore
import org.whispersystems.libsignal.state.SessionStore
import org.whispersystems.libsignal.state.SignedPreKeyRecord
import org.whispersystems.libsignal.state.SignedPreKeyStore
import org.whispersystems.libsignal.state.impl.InMemoryIdentityKeyStore
import org.whispersystems.libsignal.state.impl.InMemoryPreKeyStore
import org.whispersystems.libsignal.state.impl.InMemorySessionStore
import org.whispersystems.libsignal.state.impl.InMemorySignedPreKeyStore

interface SignalProtocol {
val identityKeyPair: IdentityKeyPair

val registrationId: Int

val preKeys: List<PreKeyRecord>

val signedPreKey: SignedPreKeyRecord

val sessionStore: SessionStore
get() = InMemorySessionStore()

val preKeyStore: PreKeyStore
get() = InMemoryPreKeyStore()

val signedPreKeyStore: SignedPreKeyStore
get() = InMemorySignedPreKeyStore()

val identityKeyStore: IdentityKeyStore
get() = InMemoryIdentityKeyStore(identityKeyPair, registrationId)
}
12 changes: 12 additions & 0 deletions composeApp/src/commonMain/kotlin/com/clipevery/utils/JsonUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.clipevery.utils

import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

inline fun <reified T> readJson(json: String): T {
return Json.decodeFromString<T>(json)
}

inline fun <reified T : Any> writeJson(obj: T): String {
return Json.encodeToString(obj)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.clipevery.encrypt

import com.clipevery.utils.readJson
import org.whispersystems.libsignal.IdentityKeyPair
import org.whispersystems.libsignal.state.PreKeyRecord
import org.whispersystems.libsignal.state.SignedPreKeyRecord
import org.whispersystems.libsignal.util.KeyHelper

class DesktopSignalProtocol(override val identityKeyPair: IdentityKeyPair,
override val registrationId: Int,
override val preKeys: List<PreKeyRecord>,
override val signedPreKey: SignedPreKeyRecord
): SignalProtocol {

constructor(): this(KeyHelper.generateIdentityKeyPair(),
KeyHelper.generateRegistrationId(false),
KeyHelper.generatePreKeys(0, 5),
KeyHelper.generateSignedPreKey(KeyHelper.generateIdentityKeyPair(), 5))
}


data class StringEncodeSignalProtocol(val identityKeyPairStr: String,
val registrationIdStr: Int,
val preKeysStr: List<String>,
val signedPreKeyStr: String)


fun readSignalProtocol(data: String): SignalProtocol {
val stringEncodeSignalProtocol = readJson<StringEncodeSignalProtocol>(data)

val identityKeyPair = IdentityKeyPair(asciiStringToBytes(stringEncodeSignalProtocol.identityKeyPairStr))

val registrationId = stringEncodeSignalProtocol.registrationIdStr

val preKeys = stringEncodeSignalProtocol.preKeysStr.map { PreKeyRecord(asciiStringToBytes(it)) }

val signedPreKey = SignedPreKeyRecord(asciiStringToBytes(stringEncodeSignalProtocol.signedPreKeyStr))

return DesktopSignalProtocol(identityKeyPair, registrationId, preKeys, signedPreKey)
}

fun writeSignalProtocol(signalProtocol: SignalProtocol): StringEncodeSignalProtocol {
val identityKeyPairStr = bytesToAsciiString(signalProtocol.identityKeyPair.serialize())

val registrationIdStr = signalProtocol.registrationId

val preKeysStr = signalProtocol.preKeys.map { bytesToAsciiString(it.serialize()) }

val signedPreKeyStr = bytesToAsciiString(signalProtocol.signedPreKey.serialize())

return StringEncodeSignalProtocol(identityKeyPairStr, registrationIdStr, preKeysStr, signedPreKeyStr)
}

fun bytesToAsciiString(bytes: ByteArray): String {
return bytes.joinToString(separator = "") { it.toInt().toChar().toString() }
}

fun asciiStringToBytes(str: String): ByteArray {
return str.map { it.code.toByte() }.toByteArray()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.clipevery.encrypt

import org.whispersystems.libsignal.SignalProtocolAddress
import org.whispersystems.libsignal.state.SessionRecord
import org.whispersystems.libsignal.state.SessionStore

class MacSessionStore: SessionStore {
override fun loadSession(address: SignalProtocolAddress?): SessionRecord {
TODO("Not yet implemented")
}

override fun getSubDeviceSessions(name: String?): MutableList<Int> {
TODO("Not yet implemented")
}

override fun storeSession(address: SignalProtocolAddress?, record: SessionRecord?) {
TODO("Not yet implemented")
}

override fun containsSession(address: SignalProtocolAddress?): Boolean {
TODO("Not yet implemented")
}

override fun deleteSession(address: SignalProtocolAddress?) {
TODO("Not yet implemented")
}

override fun deleteAllSessions(name: String?) {
TODO("Not yet implemented")
}
}
3 changes: 3 additions & 0 deletions composeApp/src/desktopMain/kotlin/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.clipevery.ClipeveryApp
import com.clipevery.Dependencies
import com.clipevery.config.ConfigManager
import com.clipevery.config.FileType
import com.clipevery.encrypt.SignalProtocol
import com.clipevery.log.initLogger
import com.clipevery.net.ClipServer
import com.clipevery.net.DesktopClipServer
Expand Down Expand Up @@ -69,6 +70,8 @@ private fun getDependencies(
return DesktopOneFilePersist(path)
}
}
override val signalProtocol: SignalProtocol
get() = TODO("Not yet implemented")

override val configManager: ConfigManager = object : ConfigManager(ioScope) {

Expand Down

0 comments on commit 264424d

Please sign in to comment.