From 8e0b527ac72c82c7ec0e1580bed3f9dff6206645 Mon Sep 17 00:00:00 2001 From: Yiqun Zhang Date: Sun, 3 Dec 2023 22:00:04 +0800 Subject: [PATCH] :sparkles: Impl secret key storage capability (#134) --- .../encrypt/CreateSignalProtocolState.kt | 9 -- .../com/clipevery/encrypt/SignalProtocol.kt | 35 ----- .../encrypt/SignalProtocolFactory.kt | 6 - .../encrypt/SignalProtocolWithState.kt | 4 - .../kotlin/com/clipevery/model/AppConfig.kt | 2 + .../kotlin/com/clipevery/model/AppInfo.kt | 4 +- .../kotlin/com/clipevery/model/SyncInfo.kt | 5 +- .../{ => presist}/data/DriverFactory.kt | 5 +- .../kotlin/com/clipevery/ui/SyncUI.kt | 7 +- .../kotlin/com/clipevery/ui/TabsUI.kt | 2 +- .../com/clipevery/data/IdentityKey.sq | 15 +++ .../sqldelight/com/clipevery/data/PreKey.sq | 16 +++ .../com/clipevery/data/SignedPreKey.sq | 19 +++ .../sqldelight/com/clipevery/data/Sync.sq | 36 ++++- .../encrypt/DesktopSignalProtocol.kt | 98 -------------- .../encrypt/DesktopSignalProtocolFactory.kt | 125 ------------------ .../desktopMain/kotlin/com/clipevery/main.kt | 27 ++-- .../clipevery/model/DesktopAppInfoFactory.kt | 22 ++- .../data/DriverFactory.desktop.kt | 2 +- .../presist/data/IdentityKeyStore.kt | 79 +++++++++++ .../com/clipevery/presist/data/PreKeyStore.kt | 30 +++++ .../clipevery/presist/data/SessionStore.kt | 51 +++++++ .../presist/data/SignedPreKeyStore.kt | 37 ++++++ .../presist/{ => file}/DesktopFilePersist.kt | 4 +- .../{ => file}/DesktopOneFilePersist.kt | 3 +- .../kotlin/com/clipevery/utils/QRCodeUtils.kt | 6 +- .../clipevery/windows/WindowDapiHelperTest.kt | 46 ------- 27 files changed, 338 insertions(+), 357 deletions(-) delete mode 100644 composeApp/src/commonMain/kotlin/com/clipevery/encrypt/CreateSignalProtocolState.kt delete mode 100644 composeApp/src/commonMain/kotlin/com/clipevery/encrypt/SignalProtocol.kt delete mode 100644 composeApp/src/commonMain/kotlin/com/clipevery/encrypt/SignalProtocolFactory.kt delete mode 100644 composeApp/src/commonMain/kotlin/com/clipevery/encrypt/SignalProtocolWithState.kt rename composeApp/src/commonMain/kotlin/com/clipevery/{ => presist}/data/DriverFactory.kt (78%) create mode 100644 composeApp/src/commonMain/sqldelight/com/clipevery/data/IdentityKey.sq create mode 100644 composeApp/src/commonMain/sqldelight/com/clipevery/data/PreKey.sq create mode 100644 composeApp/src/commonMain/sqldelight/com/clipevery/data/SignedPreKey.sq delete mode 100644 composeApp/src/desktopMain/kotlin/com/clipevery/encrypt/DesktopSignalProtocol.kt delete mode 100644 composeApp/src/desktopMain/kotlin/com/clipevery/encrypt/DesktopSignalProtocolFactory.kt rename composeApp/src/desktopMain/kotlin/com/clipevery/{ => presist}/data/DriverFactory.desktop.kt (94%) create mode 100644 composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/IdentityKeyStore.kt create mode 100644 composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/PreKeyStore.kt create mode 100644 composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/SessionStore.kt create mode 100644 composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/SignedPreKeyStore.kt rename composeApp/src/desktopMain/kotlin/com/clipevery/presist/{ => file}/DesktopFilePersist.kt (73%) rename composeApp/src/desktopMain/kotlin/com/clipevery/presist/{ => file}/DesktopOneFilePersist.kt (93%) diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/encrypt/CreateSignalProtocolState.kt b/composeApp/src/commonMain/kotlin/com/clipevery/encrypt/CreateSignalProtocolState.kt deleted file mode 100644 index a3ca71ace..000000000 --- a/composeApp/src/commonMain/kotlin/com/clipevery/encrypt/CreateSignalProtocolState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.clipevery.encrypt - -enum class CreateSignalProtocolState { - - NEW_GENERATE, - DELETE_GENERATE, - EXISTING, - -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/encrypt/SignalProtocol.kt b/composeApp/src/commonMain/kotlin/com/clipevery/encrypt/SignalProtocol.kt deleted file mode 100644 index f5e851f86..000000000 --- a/composeApp/src/commonMain/kotlin/com/clipevery/encrypt/SignalProtocol.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.clipevery.encrypt - -import org.signal.libsignal.protocol.IdentityKeyPair -import org.signal.libsignal.protocol.state.IdentityKeyStore -import org.signal.libsignal.protocol.state.PreKeyRecord -import org.signal.libsignal.protocol.state.PreKeyStore -import org.signal.libsignal.protocol.state.SessionStore -import org.signal.libsignal.protocol.state.SignedPreKeyRecord -import org.signal.libsignal.protocol.state.SignedPreKeyStore -import org.signal.libsignal.protocol.state.impl.InMemoryIdentityKeyStore -import org.signal.libsignal.protocol.state.impl.InMemoryPreKeyStore -import org.signal.libsignal.protocol.state.impl.InMemorySessionStore -import org.signal.libsignal.protocol.state.impl.InMemorySignedPreKeyStore - -interface SignalProtocol { - val identityKeyPair: IdentityKeyPair - - val registrationId: Int - - val preKeys: List - - 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) -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/encrypt/SignalProtocolFactory.kt b/composeApp/src/commonMain/kotlin/com/clipevery/encrypt/SignalProtocolFactory.kt deleted file mode 100644 index 4591c9b64..000000000 --- a/composeApp/src/commonMain/kotlin/com/clipevery/encrypt/SignalProtocolFactory.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.clipevery.encrypt - -interface SignalProtocolFactory { - - fun createSignalProtocol(): SignalProtocolWithState -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/encrypt/SignalProtocolWithState.kt b/composeApp/src/commonMain/kotlin/com/clipevery/encrypt/SignalProtocolWithState.kt deleted file mode 100644 index 7eddfc0e1..000000000 --- a/composeApp/src/commonMain/kotlin/com/clipevery/encrypt/SignalProtocolWithState.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.clipevery.encrypt - -data class SignalProtocolWithState(val signalProtocol: SignalProtocol, - val state: CreateSignalProtocolState) diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/model/AppConfig.kt b/composeApp/src/commonMain/kotlin/com/clipevery/model/AppConfig.kt index 87822f82c..7a9310680 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/model/AppConfig.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/model/AppConfig.kt @@ -2,9 +2,11 @@ package com.clipevery.model import kotlinx.serialization.Serializable import java.util.Locale +import java.util.UUID @Serializable data class AppConfig( + val appInstanceId: String = UUID.randomUUID().toString(), val bindingState: Boolean = false, val language: String = Locale.getDefault().language, val isFollowSystem: Boolean = true, diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/model/AppInfo.kt b/composeApp/src/commonMain/kotlin/com/clipevery/model/AppInfo.kt index db2c00def..c360198de 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/model/AppInfo.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/model/AppInfo.kt @@ -1,7 +1,9 @@ package com.clipevery.model +const val AppName: String = "Clipevery" + data class AppInfo( - val appName: String = "Clipevery", + val appInstanceId: String, val appVersion: String, val userName: String ) diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/model/SyncInfo.kt b/composeApp/src/commonMain/kotlin/com/clipevery/model/SyncInfo.kt index f4ebeca93..316cb9f82 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/model/SyncInfo.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/model/SyncInfo.kt @@ -1,6 +1,9 @@ package com.clipevery.model +import org.signal.libsignal.protocol.SignalProtocolAddress + data class SyncInfo( val appInfo: AppInfo, val endpointInfo: EndpointInfo, - val state: SyncState) + val state: SyncState +): SignalProtocolAddress(appInfo.appInstanceId, 1) diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/data/DriverFactory.kt b/composeApp/src/commonMain/kotlin/com/clipevery/presist/data/DriverFactory.kt similarity index 78% rename from composeApp/src/commonMain/kotlin/com/clipevery/data/DriverFactory.kt rename to composeApp/src/commonMain/kotlin/com/clipevery/presist/data/DriverFactory.kt index 553b94a52..3334ede57 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/data/DriverFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/presist/data/DriverFactory.kt @@ -1,4 +1,4 @@ -package com.clipevery.data +package com.clipevery.presist.data import app.cash.sqldelight.db.SqlDriver import com.clipevery.Database @@ -9,8 +9,7 @@ expect class DriverFactory { fun createDatabase(driverFactory: DriverFactory): Database { val driver = driverFactory.createDriver() - val database = Database(driver) // Do more work with the database (see below). - return database; + return Database(driver); } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/ui/SyncUI.kt b/composeApp/src/commonMain/kotlin/com/clipevery/ui/SyncUI.kt index 37f5e4525..580b4f65e 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/ui/SyncUI.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/ui/SyncUI.kt @@ -18,12 +18,13 @@ import com.clipevery.platform.Platform import com.clipevery.platform.currentPlatform @Composable -fun Devices() { +fun Syncs() { val current = LocalKoinApplication.current val deviceFactory = current.koin.get() val deviceInfo = deviceFactory.createDeviceInfo() SyncItem(SyncInfo( appInfo = AppInfo( + appInstanceId = "1234567890", appVersion = "1.0.0", userName = "John Doe" ), @@ -39,6 +40,7 @@ fun Devices() { Divider(modifier = Modifier.fillMaxWidth()) SyncItem(SyncInfo( appInfo = AppInfo( + appInstanceId = "1234567890", appVersion = "1.0.0", userName = "John Doe" ), @@ -63,6 +65,7 @@ fun Devices() { Divider(modifier = Modifier.fillMaxWidth()) SyncItem(SyncInfo( appInfo = AppInfo( + appInstanceId = "1234567890", appVersion = "1.0.0", userName = "John Doe" ), @@ -89,7 +92,7 @@ fun Devices() { @Composable fun test() { Column(modifier = Modifier.fillMaxSize()) { - Devices() + Syncs() } } diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/ui/TabsUI.kt b/composeApp/src/commonMain/kotlin/com/clipevery/ui/TabsUI.kt index 89b5bd470..07400f8fe 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/ui/TabsUI.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/ui/TabsUI.kt @@ -66,7 +66,7 @@ fun TabsUI() { Column(modifier = Modifier.fillMaxSize()) { when (selectedTabIndex) { 0 -> ClipPreview() - 1 -> Devices() + 1 -> Syncs() 2 -> { if (!config.value.bindingState) { bindingQRCode() diff --git a/composeApp/src/commonMain/sqldelight/com/clipevery/data/IdentityKey.sq b/composeApp/src/commonMain/sqldelight/com/clipevery/data/IdentityKey.sq new file mode 100644 index 000000000..5f8296bbf --- /dev/null +++ b/composeApp/src/commonMain/sqldelight/com/clipevery/data/IdentityKey.sq @@ -0,0 +1,15 @@ +CREATE TABLE identityKey ( + app_instance_id TEXT NOT NULL PRIMARY KEY, + registrationId INTEGER NOT NULL, + serialized BLOB NOT NULL +); + +select: +SELECT * FROM identityKey WHERE app_instance_id = ?; + +tryInit: +INSERT OR FAIL INTO identityKey (app_instance_id, registrationId, serialized) VALUES (?, ?, ?); + +update: +UPDATE identityKey SET serialized = ? WHERE app_instance_id = ? AND registrationId = ?; + diff --git a/composeApp/src/commonMain/sqldelight/com/clipevery/data/PreKey.sq b/composeApp/src/commonMain/sqldelight/com/clipevery/data/PreKey.sq new file mode 100644 index 000000000..f71273bb7 --- /dev/null +++ b/composeApp/src/commonMain/sqldelight/com/clipevery/data/PreKey.sq @@ -0,0 +1,16 @@ +CREATE TABLE preKey ( + id INTEGER PRIMARY KEY, + serialized BLOB NOT NULL +); + +selectById: +SELECT * FROM preKey WHERE id = ?; + +insert: +INSERT OR REPLACE INTO preKey (id, serialized) VALUES (?, ?); + +count: +SELECT count(1) FROM preKey WHERE id = ?; + +delete: +DELETE FROM preKey WHERE id = ?; \ No newline at end of file diff --git a/composeApp/src/commonMain/sqldelight/com/clipevery/data/SignedPreKey.sq b/composeApp/src/commonMain/sqldelight/com/clipevery/data/SignedPreKey.sq new file mode 100644 index 000000000..c60b20eab --- /dev/null +++ b/composeApp/src/commonMain/sqldelight/com/clipevery/data/SignedPreKey.sq @@ -0,0 +1,19 @@ +CREATE TABLE signedPreKey ( + id INTEGER PRIMARY KEY, + serialized BLOB NOT NULL +); + +selectById: +SELECT * FROM signedPreKey WHERE id = ?; + +selectAll: +SELECT * FROM signedPreKey; + +insert: +INSERT OR REPLACE INTO signedPreKey (id, serialized) VALUES (?, ?); + +count: +SELECT COUNT(*) FROM signedPreKey WHERE id = ?; + +delete: +DELETE FROM signedPreKey WHERE id = ?; \ No newline at end of file diff --git a/composeApp/src/commonMain/sqldelight/com/clipevery/data/Sync.sq b/composeApp/src/commonMain/sqldelight/com/clipevery/data/Sync.sq index 6d4412a5a..805e0b989 100644 --- a/composeApp/src/commonMain/sqldelight/com/clipevery/data/Sync.sq +++ b/composeApp/src/commonMain/sqldelight/com/clipevery/data/Sync.sq @@ -1,5 +1,5 @@ CREATE TABLE syncInfo ( - app_instance_name TEXT NOT NULL PRIMARY KEY, + app_instance_id TEXT NOT NULL PRIMARY KEY, app_version TEXT NOT NULL, app_user_name TEXT NOT NULL, device_id TEXT NOT NULL, @@ -10,7 +10,9 @@ CREATE TABLE syncInfo ( platform_name TEXT NOT NULL, platform_arch TEXT NOT NULL, platform_bit_mode INTEGER NOT NULL, - platform_version TEXT NOT NULL + platform_version TEXT NOT NULL, + public_key BLOB, + sessionRecord BLOB ); @@ -20,6 +22,32 @@ selectAll: SELECT * FROM syncInfo; +selectSessionRecord: +SELECT sessionRecord FROM syncInfo WHERE app_instance_id = ?; + +selectSessionRecords: +SELECT sessionRecord FROM syncInfo WHERE app_instance_id IN ?; + +selectPublicKey: +SELECT public_key FROM syncInfo WHERE app_instance_id = ?; + +selectSubDevice: +SELECT app_instance_id FROM syncInfo WHERE app_instance_id = ?; + insert: -INSERT INTO syncInfo(app_instance_name, app_version, app_user_name, device_id, device_name, device_state, host_address, port, platform_name, platform_arch, platform_bit_mode, platform_version) -VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); \ No newline at end of file +INSERT INTO syncInfo(app_instance_id, app_version, app_user_name, +device_id, device_name, device_state, host_address, port, platform_name, +platform_arch, platform_bit_mode, platform_version) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + +updatePublicKey: +UPDATE syncInfo SET public_key = ? WHERE app_instance_id = ?; + +updateSessionRecord: +UPDATE syncInfo SET sessionRecord = ? WHERE app_instance_id = ?; + +count: +SELECT COUNT(1) FROM syncInfo WHERE app_instance_id = ?; + +delete: +DELETE FROM syncInfo WHERE app_instance_id = ?; diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/encrypt/DesktopSignalProtocol.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/encrypt/DesktopSignalProtocol.kt deleted file mode 100644 index b74c9b034..000000000 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/encrypt/DesktopSignalProtocol.kt +++ /dev/null @@ -1,98 +0,0 @@ -package com.clipevery.encrypt - -import org.signal.libsignal.protocol.IdentityKeyPair -import org.signal.libsignal.protocol.InvalidKeyException -import org.signal.libsignal.protocol.ecc.Curve -import org.signal.libsignal.protocol.ecc.ECKeyPair -import org.signal.libsignal.protocol.state.PreKeyRecord -import org.signal.libsignal.protocol.state.SignedPreKeyRecord -import org.signal.libsignal.protocol.util.KeyHelper -import org.signal.libsignal.protocol.util.Medium -import java.io.ByteArrayOutputStream -import java.io.DataInputStream -import java.io.DataOutputStream -import java.util.LinkedList - - -fun generatePreKeys(start: Int, count: Int): List { - var newStart = start - val results: MutableList = LinkedList() - newStart-- - for (i in 0 until count) { - results.add(PreKeyRecord((newStart + i) % (Medium.MAX_VALUE - 1) + 1, Curve.generateKeyPair())) - } - return results -} - -@Throws(InvalidKeyException::class) -fun generateSignedPreKey( - identityKeyPair: IdentityKeyPair, - signedPreKeyId: Int -): SignedPreKeyRecord { - val keyPair: ECKeyPair = Curve.generateKeyPair() - val signature = - Curve.calculateSignature(identityKeyPair.privateKey, keyPair.getPublicKey().serialize()) - return SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature) -} - -class DesktopSignalProtocol(override val identityKeyPair: IdentityKeyPair, - override val registrationId: Int, - override val preKeys: List, - override val signedPreKey: SignedPreKeyRecord - ): SignalProtocol { - - constructor(): this( - IdentityKeyPair.generate(), - KeyHelper.generateRegistrationId(false), - generatePreKeys(0, 5)) - - constructor(identityKeyPair: IdentityKeyPair, registrationId: Int, preKeys: List): - this(identityKeyPair, - registrationId, - preKeys, - generateSignedPreKey(identityKeyPair, 5)) -} - -fun readSignalProtocol(data: ByteArray): SignalProtocol { - val inputStream = DataInputStream(data.inputStream()) - val identityKeyPairSize = inputStream.readInt() - val identityKeyPairBytes = inputStream.readNBytes(identityKeyPairSize) - val identityKeyPair = IdentityKeyPair(identityKeyPairBytes) - val registrationId = inputStream.readInt() - val preKeysSize = inputStream.readInt() - val preKeys = buildList { - for (i in 0 until preKeysSize) { - val preKeySize = inputStream.readInt() - val preKeyBytes = inputStream.readNBytes(preKeySize) - add(PreKeyRecord(preKeyBytes)) - } - } - val signedPreKeySize = inputStream.readInt() - val signedPreKeyBytes = inputStream.readNBytes(signedPreKeySize) - val signedPreKeyRecord = SignedPreKeyRecord(signedPreKeyBytes) - return DesktopSignalProtocol(identityKeyPair, registrationId, preKeys, signedPreKeyRecord) -} - -fun writeSignalProtocol(signalProtocol: SignalProtocol): ByteArray { - val byteStream = ByteArrayOutputStream() - val dataStream = DataOutputStream(byteStream) - val identityKeyPairBytes = signalProtocol.identityKeyPair.serialize() - val identityKeyPairSize = identityKeyPairBytes.size - dataStream.writeInt(identityKeyPairSize) - dataStream.write(identityKeyPairBytes) - dataStream.writeInt(signalProtocol.registrationId) - val preKeys = signalProtocol.preKeys - dataStream.writeInt(preKeys.size) - preKeys.forEach { - val preKeyBytes = it.serialize() - val preKeySize = preKeyBytes.size - dataStream.writeInt(preKeySize) - dataStream.write(preKeyBytes) - } - val signedPreKeyBytes = signalProtocol.signedPreKey.serialize() - val signedPreKeySize = signedPreKeyBytes.size - dataStream.writeInt(signedPreKeySize) - dataStream.write(signedPreKeyBytes) - - return byteStream.toByteArray() -} diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/encrypt/DesktopSignalProtocolFactory.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/encrypt/DesktopSignalProtocolFactory.kt deleted file mode 100644 index a7b526e68..000000000 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/encrypt/DesktopSignalProtocolFactory.kt +++ /dev/null @@ -1,125 +0,0 @@ -package com.clipevery.encrypt - -import com.clipevery.config.FileType -import com.clipevery.model.AppInfo -import com.clipevery.macos.MacosKeychainHelper -import com.clipevery.path.getPathProvider -import com.clipevery.platform.currentPlatform -import com.clipevery.presist.DesktopOneFilePersist -import com.clipevery.windows.WindowDapiHelper -import io.github.oshai.kotlinlogging.KotlinLogging - -val logger = KotlinLogging.logger {} - -fun getSignalProtocolFactory(appInfo: AppInfo): SignalProtocolFactory { - val currentPlatform = currentPlatform() - return if (currentPlatform.isMacos()) { - MacosSignalProtocolFactory(appInfo) - } else if (currentPlatform.isWindows()) { - WindowsSignalProtocolFactory() - } else { - throw IllegalStateException("Unknown platform: ${currentPlatform.name}") - } -} - -class MacosSignalProtocolFactory(private val appInfo: AppInfo): SignalProtocolFactory { - - private val filePersist = DesktopOneFilePersist(getPathProvider() - .resolve("signal.data", FileType.ENCRYPT)) - - override fun createSignalProtocol(): SignalProtocolWithState { - val file = filePersist.path.toFile() - var deleteOldSignalProtocol = false - if (file.exists()) { - logger.info { "Found signalProtocol encrypt file" } - val bytes = file.readBytes() - val password = MacosKeychainHelper.getPassword(appInfo.appName, appInfo.userName) - - password?.let { - logger.info { "Found password in keychain by ${appInfo.appName} ${appInfo.userName}" } - try { - val secretKey = stringToSecretKey(it) - val decryptData = decryptData(secretKey, bytes) - return SignalProtocolWithState( - readSignalProtocol(decryptData), - CreateSignalProtocolState.EXISTING - ) - } catch (e: Exception) { - logger.error(e) { "Failed to decrypt signalProtocol" } - } - } - - deleteOldSignalProtocol = true - if (file.delete()) { - logger.info { "Delete signalProtocol encrypt file" } - } - - } else { - logger.info { "No found signalProtocol encrypt file" } - } - - logger.info { "Creating new SignalProtocol" } - val signalProtocol = DesktopSignalProtocol() - val data = writeSignalProtocol(signalProtocol) - val password = MacosKeychainHelper.getPassword(appInfo.appName, appInfo.userName) - - val secretKey = password?.let { - logger.info { "Found password in keychain by ${appInfo.appName} ${appInfo.userName}" } - stringToSecretKey(it) - } ?: run { - logger.info { "Generating new password in keychain by ${appInfo.appName} ${appInfo.userName}" } - val secretKey = generateAESKey() - MacosKeychainHelper.setPassword(appInfo.appName, appInfo.userName, secretKeyToString(secretKey)) - secretKey - } - - val encryptData = encryptData(secretKey, data) - filePersist.saveBytes(encryptData) - return SignalProtocolWithState(signalProtocol, - if (deleteOldSignalProtocol) CreateSignalProtocolState.DELETE_GENERATE - else CreateSignalProtocolState.NEW_GENERATE) - } -} - - -class WindowsSignalProtocolFactory : SignalProtocolFactory { - - private val filePersist = DesktopOneFilePersist(getPathProvider() - .resolve("signal.data", FileType.ENCRYPT)) - - override fun createSignalProtocol(): SignalProtocolWithState { - val file = filePersist.path.toFile() - var deleteOldSignalProtocol = false - if (file.exists()) { - logger.info { "Found signalProtocol encrypt file" } - filePersist.readBytes()?.let { - try { - val decryptData = WindowDapiHelper.decryptData(it) - decryptData?.let { byteArray -> - return SignalProtocolWithState( - readSignalProtocol(byteArray), - CreateSignalProtocolState.EXISTING - ) - } - } catch (e: Exception) { - logger.error(e) { "Failed to decrypt signalProtocol" } - } - } - deleteOldSignalProtocol = true - if (file.delete()) { - logger.info { "Delete signalProtocol encrypt file" } - } - } else { - logger.info { "No found signalProtocol encrypt file" } - } - - logger.info { "Creating new SignalProtocol" } - val signalProtocol = DesktopSignalProtocol() - val data = writeSignalProtocol(signalProtocol) - val encryptData = WindowDapiHelper.encryptData(data) - filePersist.saveBytes(encryptData!!) - return SignalProtocolWithState(signalProtocol, - if (deleteOldSignalProtocol) CreateSignalProtocolState.DELETE_GENERATE - else CreateSignalProtocolState.NEW_GENERATE) - } -} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/main.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/main.kt index 59c9df790..7f1b0ffdf 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/main.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/main.kt @@ -20,26 +20,28 @@ import com.clipevery.clip.getDesktopClipboardService import com.clipevery.config.ConfigManager import com.clipevery.config.DefaultConfigManager import com.clipevery.config.FileType -import com.clipevery.data.DriverFactory import com.clipevery.device.DesktopDeviceInfoFactory import com.clipevery.device.DeviceInfoFactory -import com.clipevery.encrypt.SignalProtocol -import com.clipevery.encrypt.SignalProtocolWithState -import com.clipevery.encrypt.getSignalProtocolFactory import com.clipevery.i18n.GlobalCopywriter import com.clipevery.i18n.GlobalCopywriterImpl import com.clipevery.listen.GlobalListener import com.clipevery.log.initLogger import com.clipevery.model.AppInfo -import com.clipevery.model.getAppInfoFactory +import com.clipevery.model.DesktopAppInfoFactory import com.clipevery.net.ClipClient import com.clipevery.net.ClipServer import com.clipevery.net.DesktopClipClient import com.clipevery.net.DesktopClipServer import com.clipevery.path.getPathProvider import com.clipevery.platform.currentPlatform -import com.clipevery.presist.DesktopFilePersist import com.clipevery.presist.FilePersist +import com.clipevery.presist.data.DesktopIdentityKeyStore +import com.clipevery.presist.data.DesktopPreKeyStore +import com.clipevery.presist.data.DesktopSessionStore +import com.clipevery.presist.data.DesktopSignedPreKeyStore +import com.clipevery.presist.data.DriverFactory +import com.clipevery.presist.data.createDatabase +import com.clipevery.presist.file.DesktopFilePersist import com.clipevery.ui.DesktopThemeDetector import com.clipevery.ui.ThemeDetector import com.clipevery.ui.getTrayMouseAdapter @@ -53,6 +55,10 @@ import kotlinx.coroutines.CoroutineScope import org.koin.core.KoinApplication import org.koin.core.context.GlobalContext.startKoin import org.koin.dsl.module +import org.signal.libsignal.protocol.state.IdentityKeyStore +import org.signal.libsignal.protocol.state.PreKeyStore +import org.signal.libsignal.protocol.state.SessionStore +import org.signal.libsignal.protocol.state.SignedPreKeyStore import kotlin.io.path.pathString @@ -61,11 +67,9 @@ val appUI = initAppUI() fun initKoinApplication(ioScope: CoroutineScope): KoinApplication { val appModule = module { single { ioScope } - single { getAppInfoFactory().createAppInfo() } + single { DesktopAppInfoFactory(get()).createAppInfo() } single { DesktopFilePersist() } single { DefaultConfigManager(get()).initConfig() } - single { getSignalProtocolFactory(get()).createSignalProtocol() } - single { get().signalProtocol } single { DesktopClipServer().start() } single { DesktopClipClient() } single { DesktopDeviceInfoFactory() } @@ -76,6 +80,11 @@ fun initKoinApplication(ioScope: CoroutineScope): KoinApplication { single { GlobalListener() } single { DriverFactory() } single { DesktopThemeDetector(get()) } + single { DesktopIdentityKeyStore(get(), get()) } + single { DesktopPreKeyStore(get()) } + single { DesktopSessionStore(get()) } + single { DesktopSignedPreKeyStore(get()) } + single { createDatabase(DriverFactory()) } } return startKoin { modules(appModule) diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/model/DesktopAppInfoFactory.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/model/DesktopAppInfoFactory.kt index f222ee723..62c2ae850 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/model/DesktopAppInfoFactory.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/model/DesktopAppInfoFactory.kt @@ -1,6 +1,7 @@ package com.clipevery.model import com.clipevery.AppInfoFactory +import com.clipevery.config.ConfigManager import com.clipevery.platform.currentPlatform import io.github.oshai.kotlinlogging.KotlinLogging import java.io.IOException @@ -10,26 +11,33 @@ import java.util.Properties val logger = KotlinLogging.logger {} -fun getAppInfoFactory(): AppInfoFactory { +class DesktopAppInfoFactory(private val configManager: ConfigManager): AppInfoFactory { + override fun createAppInfo(): AppInfo { + val appInstanceId = configManager.config.appInstanceId + return getAppInfoFactory(appInstanceId).createAppInfo() + } +} + +fun getAppInfoFactory(appInstanceId: String): AppInfoFactory { val platform = currentPlatform() return if (platform.isMacos()) { - MacosAppInfoFactory() + MacosAppInfoFactory(appInstanceId) } else if (platform.isWindows()) { - WindowsAppInfoFactory() + WindowsAppInfoFactory(appInstanceId) } else { throw IllegalStateException("Unknown platform: ${platform.name}") } } -class MacosAppInfoFactory: AppInfoFactory { +class MacosAppInfoFactory(private val appInstanceId: String): AppInfoFactory { override fun createAppInfo(): AppInfo { - return AppInfo(appVersion = getVersion(), userName = getUserName()) + return AppInfo(appInstanceId = appInstanceId, appVersion = getVersion(), userName = getUserName()) } } -class WindowsAppInfoFactory: AppInfoFactory { +class WindowsAppInfoFactory(private val appInstanceId: String): AppInfoFactory { override fun createAppInfo(): AppInfo { - return AppInfo(appVersion = getVersion(), userName = getUserName()) + return AppInfo(appInstanceId = appInstanceId, appVersion = getVersion(), userName = getUserName()) } } diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/data/DriverFactory.desktop.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/DriverFactory.desktop.kt similarity index 94% rename from composeApp/src/desktopMain/kotlin/com/clipevery/data/DriverFactory.desktop.kt rename to composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/DriverFactory.desktop.kt index 627a70bd5..26d3f35d4 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/data/DriverFactory.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/DriverFactory.desktop.kt @@ -1,4 +1,4 @@ -package com.clipevery.data +package com.clipevery.presist.data import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/IdentityKeyStore.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/IdentityKeyStore.kt new file mode 100644 index 000000000..14538df79 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/IdentityKeyStore.kt @@ -0,0 +1,79 @@ +package com.clipevery.presist.data + +import com.clipevery.Database +import com.clipevery.model.AppInfo +import io.github.oshai.kotlinlogging.KotlinLogging +import org.signal.libsignal.protocol.IdentityKey +import org.signal.libsignal.protocol.IdentityKeyPair +import org.signal.libsignal.protocol.IdentityKeyPair.generate +import org.signal.libsignal.protocol.SignalProtocolAddress +import org.signal.libsignal.protocol.state.IdentityKeyStore +import org.signal.libsignal.protocol.util.KeyHelper + +class DesktopIdentityKeyStore(private val database: Database, + private val appInfo: AppInfo): IdentityKeyStore { + + + private val logger = KotlinLogging.logger {} + + private val identityKeyPair: IdentityKeyPair + + private val registrationId: Int + + init { + val identityKeyPair = generate() + val registrationId = KeyHelper.generateRegistrationId(false) + try { + database.identityKeyQueries.tryInit(appInfo.appInstanceId, registrationId.toLong(), identityKeyPair.serialize()) + logger.info { "init identityKey success, appInstanceId = ${appInfo.appInstanceId}" } + } catch (ignore: Throwable) { + logger.info { "identityKey exist, appInstanceId = ${appInfo.appInstanceId}" } + } + val identityKey = database.identityKeyQueries.select(appInfo.appInstanceId).executeAsOneOrNull() + if (identityKey == null) { + logger.error { "Failed to get identityKey, appInstanceId = ${appInfo.appInstanceId}" } + this.identityKeyPair = identityKeyPair + this.registrationId = registrationId + } else { + logger.info { "get identityKey success, appInstanceId = ${appInfo.appInstanceId}" } + this.identityKeyPair = IdentityKeyPair(identityKey.serialized) + this.registrationId = identityKey.registrationId.toInt() + } + } + + override fun getIdentityKeyPair(): IdentityKeyPair { + return identityKeyPair + } + + override fun getLocalRegistrationId(): Int { + return registrationId + } + + override fun saveIdentity(address: SignalProtocolAddress, identityKey: IdentityKey): Boolean { + return try { + database.identityKeyQueries.update(identityKey.serialize(), appInfo.appInstanceId, registrationId.toLong()) + true + } catch (e: Exception) { + false + } + } + + override fun isTrustedIdentity( + address: SignalProtocolAddress, + identityKey: IdentityKey, + direction: IdentityKeyStore.Direction + ): Boolean { + val identity: IdentityKey? = getIdentity(address) + return identity?.let { it == identityKey } ?: true + } + + override fun getIdentity(address: SignalProtocolAddress): IdentityKey? { + return database.syncQueries.selectPublicKey(address.name) + .executeAsOneOrNull()?.let { selectPublicKey -> + return selectPublicKey.public_key?.let { + return IdentityKey(it) + } + } + } + +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/PreKeyStore.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/PreKeyStore.kt new file mode 100644 index 000000000..61e7afe2d --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/PreKeyStore.kt @@ -0,0 +1,30 @@ +package com.clipevery.presist.data + +import com.clipevery.Database +import com.clipevery.data.PreKey +import org.signal.libsignal.protocol.InvalidKeyIdException +import org.signal.libsignal.protocol.state.PreKeyRecord +import org.signal.libsignal.protocol.state.PreKeyStore + +class DesktopPreKeyStore(private val database: Database): PreKeyStore { + + override fun loadPreKey(preKeyId: Int): PreKeyRecord { + val preKey: PreKey = database.preKeyQueries.selectById(preKeyId.toLong()) + .executeAsOneOrNull() ?: throw InvalidKeyIdException("No such preKeyId: $preKeyId") + return PreKeyRecord(preKey.serialized) + } + + override fun storePreKey(preKeyId: Int, record: PreKeyRecord) { + database.preKeyQueries.insert(preKeyId.toLong(), record.serialize()) + } + + override fun containsPreKey(preKeyId: Int): Boolean { + return database.preKeyQueries.count(preKeyId.toLong()).executeAsOneOrNull()?.let { + return it > 0 + } ?: false + } + + override fun removePreKey(keyId: Int) { + database.preKeyQueries.delete(keyId.toLong()) + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/SessionStore.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/SessionStore.kt new file mode 100644 index 000000000..3b365ecc9 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/SessionStore.kt @@ -0,0 +1,51 @@ +package com.clipevery.presist.data + +import com.clipevery.Database +import org.signal.libsignal.protocol.SignalProtocolAddress +import org.signal.libsignal.protocol.state.SessionRecord +import org.signal.libsignal.protocol.state.SessionStore + +class DesktopSessionStore(private val database: Database): SessionStore { + + override fun loadSession(address: SignalProtocolAddress): SessionRecord? { + return database.syncQueries.selectSessionRecord(address.name).executeAsOneOrNull()?.let { selectSessionRecord -> + val sessionRecord: ByteArray? = selectSessionRecord.sessionRecord + return sessionRecord?.let { + return SessionRecord(it) + } + } + } + + override fun loadExistingSessions(addresses: MutableList?): MutableList { + if (addresses!!.isEmpty()) { + return mutableListOf() + } + database.syncQueries.selectSessionRecords(addresses.map { it.name }).executeAsList().let { sessionRecords -> + return sessionRecords.mapNotNull { it -> it.sessionRecord?.let { SessionRecord(it) } }.toMutableList() + } + } + + override fun getSubDeviceSessions(name: String): MutableList { + database.syncQueries.selectSubDevice(name).executeAsOneOrNull()?.let { + return mutableListOf(1) + } ?: return mutableListOf() + } + + override fun storeSession(address: SignalProtocolAddress, record: SessionRecord) { + database.syncQueries.updateSessionRecord(record.serialize(), address.name) + } + + override fun containsSession(address: SignalProtocolAddress): Boolean { + return database.syncQueries.count(address.name).executeAsOneOrNull()?.let { + return it > 0 + } ?: false + } + + override fun deleteSession(address: SignalProtocolAddress) { + database.syncQueries.delete(address.name) + } + + override fun deleteAllSessions(name: String) { + database.syncQueries.delete(name) + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/SignedPreKeyStore.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/SignedPreKeyStore.kt new file mode 100644 index 000000000..354600c10 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/data/SignedPreKeyStore.kt @@ -0,0 +1,37 @@ +package com.clipevery.presist.data + +import com.clipevery.Database +import com.clipevery.data.SignedPreKey +import org.signal.libsignal.protocol.InvalidKeyIdException +import org.signal.libsignal.protocol.state.SignedPreKeyRecord +import org.signal.libsignal.protocol.state.SignedPreKeyStore + +class DesktopSignedPreKeyStore(private val database: Database): SignedPreKeyStore { + override fun loadSignedPreKey(signedPreKeyId: Int): SignedPreKeyRecord { + val signedPreKey: SignedPreKey = database.signedPreKeyQueries.selectById(signedPreKeyId.toLong()) + .executeAsOneOrNull()?.let { + return SignedPreKeyRecord(it.serialized) + } ?: throw InvalidKeyIdException("No such signedPreKeyId: $signedPreKeyId") + return SignedPreKeyRecord(signedPreKey.serialized) + } + + override fun loadSignedPreKeys(): MutableList { + database.signedPreKeyQueries.selectAll().executeAsList().let { signedPreKeys -> + return signedPreKeys.map { SignedPreKeyRecord(it.serialized) }.toMutableList() + } + } + + override fun storeSignedPreKey(signedPreKeyId: Int, record: SignedPreKeyRecord) { + database.signedPreKeyQueries.insert(signedPreKeyId.toLong(), record.serialize()) + } + + override fun containsSignedPreKey(signedPreKeyId: Int): Boolean { + return database.signedPreKeyQueries.count(signedPreKeyId.toLong()).executeAsOneOrNull()?.let { + return it > 0 + } ?: false + } + + override fun removeSignedPreKey(signedPreKeyId: Int) { + database.signedPreKeyQueries.delete(signedPreKeyId.toLong()) + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/presist/DesktopFilePersist.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/file/DesktopFilePersist.kt similarity index 73% rename from composeApp/src/desktopMain/kotlin/com/clipevery/presist/DesktopFilePersist.kt rename to composeApp/src/desktopMain/kotlin/com/clipevery/presist/file/DesktopFilePersist.kt index 955946238..5e9ea32da 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/presist/DesktopFilePersist.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/file/DesktopFilePersist.kt @@ -1,7 +1,9 @@ -package com.clipevery.presist +package com.clipevery.presist.file import com.clipevery.path.PathProvider import com.clipevery.path.getPathProvider +import com.clipevery.presist.FilePersist +import com.clipevery.presist.OneFilePersist import java.nio.file.Path class DesktopFilePersist: FilePersist { diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/presist/DesktopOneFilePersist.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/file/DesktopOneFilePersist.kt similarity index 93% rename from composeApp/src/desktopMain/kotlin/com/clipevery/presist/DesktopOneFilePersist.kt rename to composeApp/src/desktopMain/kotlin/com/clipevery/presist/file/DesktopOneFilePersist.kt index 1411b0b5d..6b36d1f0c 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/presist/DesktopOneFilePersist.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/presist/file/DesktopOneFilePersist.kt @@ -1,5 +1,6 @@ -package com.clipevery.presist +package com.clipevery.presist.file +import com.clipevery.presist.OneFilePersist import kotlinx.serialization.json.Json import kotlinx.serialization.serializer import java.nio.file.Path diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/QRCodeUtils.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/QRCodeUtils.kt index 87fbde472..51d8c6fd7 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/QRCodeUtils.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/QRCodeUtils.kt @@ -3,17 +3,17 @@ package com.clipevery.utils import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.toComposeImageBitmap import com.clipevery.device.DeviceInfoFactory -import com.clipevery.encrypt.SignalProtocol import com.clipevery.model.RequestEndpointInfo import com.clipevery.net.ClipServer import com.google.zxing.BarcodeFormat import com.google.zxing.qrcode.QRCodeWriter +import org.signal.libsignal.protocol.state.IdentityKeyStore import java.awt.Color import java.awt.image.BufferedImage class DesktopQRCodeGenerator(private val clipServer: ClipServer, private val deviceInfoFactory: DeviceInfoFactory, - private val signalProtocol: SignalProtocol): QRCodeGenerator { + private val identityKeyStore: IdentityKeyStore): QRCodeGenerator { private var salt: Int = 0 @@ -21,7 +21,7 @@ class DesktopQRCodeGenerator(private val clipServer: ClipServer, salt = (0..Int.MAX_VALUE).random() val deviceInfo = deviceInfoFactory.createDeviceInfo() val port = clipServer.port() - val publicKey = signalProtocol.identityKeyPair.publicKey + val publicKey = identityKeyStore.identityKeyPair.publicKey return RequestEndpointInfo(deviceInfo, port, publicKey).getBase64Encode(salt) } diff --git a/composeApp/src/desktopTest/kotlin/com/clipevery/windows/WindowDapiHelperTest.kt b/composeApp/src/desktopTest/kotlin/com/clipevery/windows/WindowDapiHelperTest.kt index 2cf1ee1c4..319d4cf20 100644 --- a/composeApp/src/desktopTest/kotlin/com/clipevery/windows/WindowDapiHelperTest.kt +++ b/composeApp/src/desktopTest/kotlin/com/clipevery/windows/WindowDapiHelperTest.kt @@ -1,8 +1,5 @@ package com.clipevery.windows -import com.clipevery.encrypt.DesktopSignalProtocol -import com.clipevery.encrypt.readSignalProtocol -import com.clipevery.encrypt.writeSignalProtocol import com.clipevery.platform.currentPlatform import kotlin.test.Test import kotlin.test.assertEquals @@ -23,47 +20,4 @@ class WindowDapiHelperTest { } } - @Test - fun testSignalProtocolDapi() { - val currentPlatform = currentPlatform() - if (currentPlatform.isWindows()) { - val signalProtocol = DesktopSignalProtocol() - val data = writeSignalProtocol(signalProtocol) - val encryptData = WindowDapiHelper.encryptData(data) - assertTrue { (encryptData?.size ?: 0) > 0 } - val decryptData = WindowDapiHelper.decryptData(encryptData!!) - assertTrue { (decryptData?.size ?: 0) > 0 } - - assertTrue { data.contentEquals(decryptData!!) } - - val newSignalProtocol = readSignalProtocol(decryptData!!) - assertEquals(signalProtocol.identityKeyPair.publicKey, - newSignalProtocol.identityKeyPair.publicKey) - assertTrue { - signalProtocol.identityKeyPair.privateKey.serialize() - .contentEquals(newSignalProtocol.identityKeyPair.privateKey.serialize()) - } - assertEquals(signalProtocol.registrationId, newSignalProtocol.registrationId) - assertEquals(signalProtocol.preKeys.size, newSignalProtocol.preKeys.size) - for (i in 0 until signalProtocol.preKeys.size) { - assertEquals(signalProtocol.preKeys[i].id, newSignalProtocol.preKeys[i].id) - assertEquals(signalProtocol.preKeys[i].keyPair.publicKey, - newSignalProtocol.preKeys[i].keyPair.publicKey) - assertTrue { - signalProtocol.preKeys[i].keyPair.privateKey.serialize() - .contentEquals(newSignalProtocol.preKeys[i].keyPair.privateKey.serialize()) } - } - assertEquals(signalProtocol.signedPreKey.id, - newSignalProtocol.signedPreKey.id) - assertEquals(signalProtocol.signedPreKey.keyPair.publicKey, - newSignalProtocol.signedPreKey.keyPair.publicKey) - - assertTrue { - signalProtocol.signedPreKey.keyPair.privateKey.serialize() - .contentEquals(newSignalProtocol.signedPreKey.keyPair.privateKey.serialize()) } - - assertTrue { signalProtocol.signedPreKey.signature - .contentEquals(newSignalProtocol.signedPreKey.signature) } - } - } } \ No newline at end of file