Add microbenchmarks for message decryption.
This commit is contained in:
parent
0156e74f5a
commit
6d4906dfa8
16 changed files with 611 additions and 13 deletions
|
@ -55,7 +55,7 @@ android {
|
|||
testOptions {
|
||||
managedDevices {
|
||||
devices {
|
||||
create ("api31", ManagedVirtualDevice::class) {
|
||||
create("api31", ManagedVirtualDevice::class) {
|
||||
device = "Pixel 6"
|
||||
apiLevel = 31
|
||||
systemImageSource = "aosp"
|
||||
|
|
8
build-logic/plugins/src/main/java/ktlint.gradle.kts
Normal file
8
build-logic/plugins/src/main/java/ktlint.gradle.kts
Normal file
|
@ -0,0 +1,8 @@
|
|||
plugins {
|
||||
id("org.jlleitschuh.gradle.ktlint")
|
||||
}
|
||||
|
||||
ktlint {
|
||||
// Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
|
||||
version.set("0.47.1")
|
||||
}
|
|
@ -17,8 +17,8 @@ val signalJavaVersion: JavaVersion by extra
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
id("org.jlleitschuh.gradle.ktlint")
|
||||
id("android-constants")
|
||||
id("ktlint")
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -46,11 +46,6 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
ktlint {
|
||||
// Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
|
||||
version.set("0.47.1")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
lintChecks(project(":lintchecks"))
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ val signalJavaVersion: JavaVersion by extra
|
|||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
id("org.jlleitschuh.gradle.ktlint")
|
||||
id("ktlint")
|
||||
id("android-constants")
|
||||
}
|
||||
|
||||
|
@ -47,11 +47,6 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
ktlint {
|
||||
// Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
|
||||
version.set("0.47.1")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring(libs.android.tools.desugar)
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ buildscript {
|
|||
exclude group: 'com.squareup.wire', module: 'wire-grpc-server-generator'
|
||||
exclude group: 'io.outfoxx', module: 'swiftpoet'
|
||||
}
|
||||
classpath 'androidx.benchmark:benchmark-gradle-plugin:1.1.0-beta04'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -159,6 +159,7 @@ dependencyResolutionManagement {
|
|||
alias('espresso-core').to('androidx.test.espresso:espresso-core:3.4.0')
|
||||
alias('uiautomator').to('androidx.test.uiautomator:uiautomator:2.2.0')
|
||||
alias('androidx-benchmark-macro').to('androidx.benchmark:benchmark-macro-junit4:1.1.1')
|
||||
alias('androidx-benchmark-micro').to('androidx.benchmark:benchmark-junit4:1.1.0-beta04')
|
||||
}
|
||||
|
||||
testLibs {
|
||||
|
|
|
@ -102,6 +102,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="4c84feee2db891ff6b97d613a0d40ab96ce297b034a6927ca8479f09e82d7c2e" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.annotation" name="annotation-experimental" version="1.0.0">
|
||||
<artifact name="annotation-experimental-1.0.0.aar">
|
||||
<sha256 value="b219d2b568e7e4ba534e09f8c2fd242343df6ccbdfbbe938846f5d740e6b0b11" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.annotation" name="annotation-experimental" version="1.1.0">
|
||||
<artifact name="annotation-experimental-1.1.0.aar">
|
||||
<sha256 value="0157de61a2064047896a058080f3fd67ba57ad9a94857b3f7a363660243e3f90" origin="Generated by Gradle"/>
|
||||
|
@ -180,6 +185,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="c9468f56e05006ea151a426c54957cd0799b8b83a579d2846dd22061f33e5ecd" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.benchmark" name="benchmark-common" version="1.1.0-beta04">
|
||||
<artifact name="benchmark-common-1.1.0-beta04.aar">
|
||||
<sha256 value="0d5d97bec86870ee542da651ba3d5ac1977318bec5047205bbdb22afa7eb84be" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="benchmark-common-1.1.0-beta04.module">
|
||||
<sha256 value="83a7d93e6a941236619117f46b8911f1cdf28be6a8765fe6b8b3ee122d992a09" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.benchmark" name="benchmark-common" version="1.1.0-beta05">
|
||||
<artifact name="benchmark-common-1.1.0-beta05.aar">
|
||||
<sha256 value="685021a5a65fc92f94e84a942f029edb2fc77e89144b666c8a85e1538f35ad19" origin="Generated by Gradle"/>
|
||||
|
@ -204,6 +217,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="5c22871af95906149ea0a0ae9a30d76e93c35382dd5a1cb8df942773143325b8" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.benchmark" name="benchmark-gradle-plugin" version="1.1.0-beta04">
|
||||
<artifact name="benchmark-gradle-plugin-1.1.0-beta04.jar">
|
||||
<sha256 value="ffcad358968c938d69922f1082b637bd7ee9d7a60bfd9433df7f5b131c9c8df6" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="benchmark-gradle-plugin-1.1.0-beta04.module">
|
||||
<sha256 value="b39229f5f951ff9091d0c42d29cdd272a96112a9bd5100131a465db656456773" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.benchmark" name="benchmark-junit4" version="1.1.0-beta04">
|
||||
<artifact name="benchmark-junit4-1.1.0-beta04.aar">
|
||||
<sha256 value="6977d2a084b46a16d4e3f1b7e76ef085337cf960a1ff44939b845c22e93bf94a" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="benchmark-junit4-1.1.0-beta04.module">
|
||||
<sha256 value="e6c8b5a5b5904ee2e1f62ffb3472af4007197f429acb42003939703581056a14" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.benchmark" name="benchmark-macro" version="1.1.0-beta05">
|
||||
<artifact name="benchmark-macro-1.1.0-beta05.aar">
|
||||
<sha256 value="979733a465d24a85536d4a51d0a9bfc20d8cd6537e032f88a5fb669f25d880e7" origin="Generated by Gradle"/>
|
||||
|
@ -1184,6 +1213,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="c1753946c498b0d5d7cf341cfed661f66915c4c9deb4ed10462a08ae33b2429a" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.test" name="rules" version="1.4.0">
|
||||
<artifact name="rules-1.4.0.aar">
|
||||
<sha256 value="01401cd7d1185530f6081ce503ce611d18d8a483ea15599a16c39f15b2f8e7d8" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.test" name="runner" version="1.4.0">
|
||||
<artifact name="runner-1.4.0.aar">
|
||||
<sha256 value="e3f3d8b8d5d4a3edcacbdaa4a31bda2b0e41d3e704b02b3750466a06367ec5a0" origin="Generated by Gradle"/>
|
||||
|
@ -1267,6 +1301,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.tracing" name="tracing-ktx" version="1.0.0">
|
||||
<artifact name="tracing-ktx-1.0.0.aar">
|
||||
<sha256 value="3a918d0d2318b8cca1062cdddb2f7b4077ee2f270093850010379d579cdc8b6e" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="tracing-ktx-1.0.0.module">
|
||||
<sha256 value="f426e636a23a05db8c7fabf2b959f4dab80c9ca97b5131bf411d422555c48c18" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
|
@ -3404,6 +3441,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="17f48d41775bd84dea78e9dfed8dfbcc66af80567a5c9ec9d9608785ec820cde" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.squareup.okio" name="okio-metadata" version="2.8.0">
|
||||
<artifact name="okio-metadata-2.8.0.jar">
|
||||
<sha256 value="f7dc3883ceae2e1fe3425e1b54c5df28b8857c5fd00381b521fda60790f2952c" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="okio-metadata-2.8.0.module">
|
||||
<sha256 value="a4eef641be5e5463d2789c3c73601f9a99f36e1377ca007e30f0fee53a054cdc" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.squareup.okio" name="okio-multiplatform" version="2.8.0">
|
||||
<artifact name="okio-multiplatform-2.8.0.module">
|
||||
<sha256 value="87c4327538ba166a914171631470a6e9db2b007125bae164af6b0d47aefd34d7" origin="Generated by Gradle"/>
|
||||
|
@ -3481,6 +3526,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="b02d3bd2e4929ccb0ab36c88b569b10fc27b360711ee1718126a3b11b317d7f7" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.squareup.wire" name="wire-runtime-metadata" version="3.6.0">
|
||||
<artifact name="wire-runtime-metadata-3.6.0.jar">
|
||||
<sha256 value="89e54163f6219b177838e9f2c18d023c3ee2c01427c3b621694717f58ee4f300" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="wire-runtime-metadata-3.6.0.module">
|
||||
<sha256 value="39dcf0ac4b2ca9e8b1fe7fdc4085aeaeefce335fb059c015ca24ec5ef0ff9ec5" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.squareup.wire" name="wire-schema" version="4.4.3">
|
||||
<artifact name="wire-schema-4.4.3.module">
|
||||
<sha256 value="44c4bf54631537be1e096d09e5557f801f4e93f853ed5830168483c6cd4c832b" origin="Generated by Gradle"/>
|
||||
|
@ -4506,6 +4559,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="4800ceacb2ec0bb9959a087154b8e35318ead1ea4eba32d4bb1b9734222a7e68" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlin" name="kotlin-stdlib" version="1.6.10">
|
||||
<artifact name="kotlin-stdlib-1.6.10.jar">
|
||||
<sha256 value="5305f7a4dee7a6cb79a29c258aca93de47b49588a6dfc6da01bd8772589ea66c" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlin" name="kotlin-stdlib" version="1.6.21">
|
||||
<artifact name="kotlin-stdlib-1.6.21.jar">
|
||||
<sha256 value="739c526672bb337573b28f63afa8306eb088b0c3a0967f56d6c89f4a3012a492" origin="Generated by Gradle"/>
|
||||
|
|
1
microbenchmark/.gitignore
vendored
Normal file
1
microbenchmark/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/build
|
37
microbenchmark/benchmark-proguard-rules.pro
Normal file
37
microbenchmark/benchmark-proguard-rules.pro
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-dontobfuscate
|
||||
|
||||
-ignorewarnings
|
||||
|
||||
-keepattributes *Annotation*
|
||||
|
||||
-dontnote junit.framework.**
|
||||
-dontnote junit.runner.**
|
||||
|
||||
-dontwarn androidx.test.**
|
||||
-dontwarn org.junit.**
|
||||
-dontwarn org.hamcrest.**
|
||||
-dontwarn com.squareup.javawriter.JavaWriter
|
||||
|
||||
-keepclasseswithmembers @org.junit.runner.RunWith public class *
|
63
microbenchmark/build.gradle.kts
Normal file
63
microbenchmark/build.gradle.kts
Normal file
|
@ -0,0 +1,63 @@
|
|||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("androidx.benchmark")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("android-constants")
|
||||
id("ktlint")
|
||||
}
|
||||
|
||||
val signalBuildToolsVersion: String by extra
|
||||
val signalCompileSdkVersion: String by extra
|
||||
val signalTargetSdkVersion: Int by extra
|
||||
val signalMinSdkVersion: Int by extra
|
||||
val signalJavaVersion: JavaVersion by extra
|
||||
|
||||
android {
|
||||
namespace = "org.signal.microbenchmark"
|
||||
compileSdkVersion = signalCompileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = signalJavaVersion
|
||||
targetCompatibility = signalJavaVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = signalMinSdkVersion
|
||||
targetSdk = signalTargetSdkVersion
|
||||
|
||||
testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
|
||||
}
|
||||
|
||||
testBuildType = "release"
|
||||
buildTypes {
|
||||
debug {
|
||||
// Since isDebuggable can't be modified by gradle for library modules,
|
||||
// it must be done in a manifest - see src/androidTest/AndroidManifest.xml
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "benchmark-proguard-rules.pro")
|
||||
}
|
||||
release {
|
||||
isDefault = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
lintChecks(project(":lintchecks"))
|
||||
|
||||
// Base dependencies
|
||||
androidTestImplementation(testLibs.junit.junit)
|
||||
androidTestImplementation(benchmarkLibs.androidx.test.ext.junit)
|
||||
androidTestImplementation(benchmarkLibs.androidx.benchmark.micro)
|
||||
|
||||
// Dependencies of modules being tested
|
||||
androidTestImplementation(project(":libsignal-service"))
|
||||
androidTestImplementation(libs.libsignal.android)
|
||||
androidTestImplementation(libs.google.protobuf.javalite)
|
||||
}
|
15
microbenchmark/src/androidTest/AndroidManifest.xml
Normal file
15
microbenchmark/src/androidTest/AndroidManifest.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!--
|
||||
Important: disable debugging for accurate performance results
|
||||
|
||||
In a com.android.library project, this flag must be disabled from this
|
||||
manifest, as it is not possible to override this flag from Gradle.
|
||||
-->
|
||||
<application
|
||||
android:debuggable="false"
|
||||
tools:ignore="HardcodedDebugMode"
|
||||
tools:replace="android:debuggable" />
|
||||
</manifest>
|
|
@ -0,0 +1,89 @@
|
|||
package org.signal.microbenchmark
|
||||
|
||||
import android.util.Log
|
||||
import androidx.benchmark.junit4.BenchmarkRule
|
||||
import androidx.benchmark.junit4.measureRepeated
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.libsignal.protocol.logging.SignalProtocolLogger
|
||||
import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider
|
||||
import org.signal.util.SignalClient
|
||||
|
||||
/**
|
||||
* Benchmarks for decrypting messages.
|
||||
*
|
||||
* Note that in order to isolate all costs to just the process of decryption itself,
|
||||
* all operations are performed in in-memory stores.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ProtocolBenchmarks {
|
||||
|
||||
@get:Rule
|
||||
val benchmarkRule = BenchmarkRule()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
SignalProtocolLoggerProvider.setProvider { priority, tag, message ->
|
||||
when (priority) {
|
||||
SignalProtocolLogger.VERBOSE -> Log.v(tag, message)
|
||||
SignalProtocolLogger.DEBUG -> Log.d(tag, message)
|
||||
SignalProtocolLogger.INFO -> Log.i(tag, message)
|
||||
SignalProtocolLogger.WARN -> Log.w(tag, message)
|
||||
SignalProtocolLogger.ERROR -> Log.w(tag, message)
|
||||
SignalProtocolLogger.ASSERT -> Log.e(tag, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decrypt_unsealedSender() {
|
||||
val (alice, bob) = buildAndInitializeClients()
|
||||
|
||||
benchmarkRule.measureRepeated {
|
||||
val envelope = runWithTimingDisabled {
|
||||
alice.encryptUnsealedSender(bob)
|
||||
}
|
||||
|
||||
bob.decryptMessage(envelope)
|
||||
|
||||
// Respond so that the session ratchets
|
||||
runWithTimingDisabled {
|
||||
alice.decryptMessage(bob.encryptUnsealedSender(alice))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decrypt_sealedSender() {
|
||||
val (alice, bob) = buildAndInitializeClients()
|
||||
|
||||
benchmarkRule.measureRepeated {
|
||||
val envelope = runWithTimingDisabled {
|
||||
alice.encryptSealedSender(bob)
|
||||
}
|
||||
|
||||
bob.decryptMessage(envelope)
|
||||
|
||||
// Respond so that the session ratchets
|
||||
runWithTimingDisabled {
|
||||
alice.decryptMessage(bob.encryptSealedSender(alice))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildAndInitializeClients(): Pair<SignalClient, SignalClient> {
|
||||
val alice = SignalClient()
|
||||
val bob = SignalClient()
|
||||
|
||||
// Do initial prekey dance
|
||||
alice.initializeSession(bob)
|
||||
bob.initializeSession(alice)
|
||||
alice.decryptMessage(bob.encryptUnsealedSender(alice))
|
||||
bob.decryptMessage(alice.encryptUnsealedSender(bob))
|
||||
|
||||
return alice to bob
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
package org.signal.util
|
||||
|
||||
import org.signal.libsignal.protocol.IdentityKey
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress
|
||||
import org.signal.libsignal.protocol.groups.state.SenderKeyRecord
|
||||
import org.signal.libsignal.protocol.message.CiphertextMessage
|
||||
import org.signal.libsignal.protocol.state.IdentityKeyStore
|
||||
import org.signal.libsignal.protocol.state.PreKeyRecord
|
||||
import org.signal.libsignal.protocol.state.SessionRecord
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
|
||||
import org.whispersystems.signalservice.api.push.DistributionId
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* An in-memory datastore specifically designed for tests.
|
||||
*/
|
||||
class InMemorySignalServiceAccountDataStore : SignalServiceAccountDataStore {
|
||||
|
||||
private val identityKey: IdentityKeyPair = IdentityKeyPair.generate()
|
||||
private val identities: MutableMap<SignalProtocolAddress, IdentityKey> = mutableMapOf()
|
||||
private val oneTimePreKeys: MutableMap<Int, PreKeyRecord> = mutableMapOf()
|
||||
private val signedPreKeys: MutableMap<Int, SignedPreKeyRecord> = mutableMapOf()
|
||||
private var sessions: MutableMap<SignalProtocolAddress, SessionRecord> = mutableMapOf()
|
||||
private val senderKeys: MutableMap<SenderKeyLocator, SenderKeyRecord> = mutableMapOf()
|
||||
|
||||
override fun getIdentityKeyPair(): IdentityKeyPair {
|
||||
return identityKey
|
||||
}
|
||||
|
||||
override fun getLocalRegistrationId(): Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
override fun saveIdentity(address: SignalProtocolAddress, identityKey: IdentityKey): Boolean {
|
||||
val hadPrevious = identities.containsKey(address)
|
||||
identities[address] = identityKey
|
||||
return hadPrevious
|
||||
}
|
||||
|
||||
override fun isTrustedIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?, direction: IdentityKeyStore.Direction?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getIdentity(address: SignalProtocolAddress): IdentityKey? {
|
||||
return identities[address]
|
||||
}
|
||||
|
||||
override fun loadPreKey(preKeyId: Int): PreKeyRecord {
|
||||
return oneTimePreKeys[preKeyId]!!
|
||||
}
|
||||
|
||||
override fun storePreKey(preKeyId: Int, record: PreKeyRecord) {
|
||||
oneTimePreKeys[preKeyId] = record
|
||||
}
|
||||
|
||||
override fun containsPreKey(preKeyId: Int): Boolean {
|
||||
return oneTimePreKeys.containsKey(preKeyId)
|
||||
}
|
||||
|
||||
override fun removePreKey(preKeyId: Int) {
|
||||
oneTimePreKeys.remove(preKeyId)
|
||||
}
|
||||
|
||||
override fun loadSession(address: SignalProtocolAddress): SessionRecord {
|
||||
return sessions.getOrPut(address) { SessionRecord() }
|
||||
}
|
||||
|
||||
override fun loadExistingSessions(addresses: List<SignalProtocolAddress>): List<SessionRecord> {
|
||||
return addresses.map { sessions[it]!! }
|
||||
}
|
||||
|
||||
override fun getSubDeviceSessions(name: String): List<Int> {
|
||||
return sessions
|
||||
.filter { it.key.name == name && it.key.deviceId != 1 && it.value.isValid() }
|
||||
.map { it.key.deviceId }
|
||||
}
|
||||
|
||||
override fun storeSession(address: SignalProtocolAddress, record: SessionRecord) {
|
||||
sessions[address] = record
|
||||
}
|
||||
|
||||
override fun containsSession(address: SignalProtocolAddress): Boolean {
|
||||
return sessions[address]?.isValid() ?: false
|
||||
}
|
||||
|
||||
override fun deleteSession(address: SignalProtocolAddress) {
|
||||
sessions -= address
|
||||
}
|
||||
|
||||
override fun deleteAllSessions(name: String) {
|
||||
sessions = sessions.filter { it.key.name == name }.toMutableMap()
|
||||
}
|
||||
|
||||
override fun loadSignedPreKey(signedPreKeyId: Int): SignedPreKeyRecord {
|
||||
return signedPreKeys[signedPreKeyId]!!
|
||||
}
|
||||
|
||||
override fun loadSignedPreKeys(): List<SignedPreKeyRecord> {
|
||||
return signedPreKeys.values.toList()
|
||||
}
|
||||
|
||||
override fun storeSignedPreKey(signedPreKeyId: Int, record: SignedPreKeyRecord) {
|
||||
signedPreKeys[signedPreKeyId] = record
|
||||
}
|
||||
|
||||
override fun containsSignedPreKey(signedPreKeyId: Int): Boolean {
|
||||
return signedPreKeys.containsKey(signedPreKeyId)
|
||||
}
|
||||
|
||||
override fun removeSignedPreKey(signedPreKeyId: Int) {
|
||||
signedPreKeys -= signedPreKeyId
|
||||
}
|
||||
|
||||
override fun storeSenderKey(sender: SignalProtocolAddress, distributionId: UUID, record: SenderKeyRecord) {
|
||||
senderKeys[SenderKeyLocator(sender, distributionId)] = record
|
||||
}
|
||||
|
||||
override fun loadSenderKey(sender: SignalProtocolAddress, distributionId: UUID): SenderKeyRecord {
|
||||
return senderKeys[SenderKeyLocator(sender, distributionId)]!!
|
||||
}
|
||||
|
||||
override fun archiveSession(address: SignalProtocolAddress) {
|
||||
sessions[address]!!.archiveCurrentState()
|
||||
}
|
||||
|
||||
override fun getAllAddressesWithActiveSessions(addressNames: MutableList<String>): Set<SignalProtocolAddress> {
|
||||
return sessions
|
||||
.filter { it.key.name in addressNames }
|
||||
.filter { it.value.isValid() }
|
||||
.map { it.key }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
override fun getSenderKeySharedWith(distributionId: DistributionId): Set<SignalProtocolAddress> {
|
||||
error("Not used")
|
||||
}
|
||||
|
||||
override fun markSenderKeySharedWith(distributionId: DistributionId, addresses: Collection<SignalProtocolAddress>) {
|
||||
// Not used
|
||||
}
|
||||
|
||||
override fun clearSenderKeySharedWith(addresses: Collection<SignalProtocolAddress>) {
|
||||
// Not used
|
||||
}
|
||||
|
||||
override fun isMultiDevice(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun SessionRecord.isValid(): Boolean {
|
||||
return this.hasSenderChain() && this.sessionVersion == CiphertextMessage.CURRENT_VERSION
|
||||
}
|
||||
|
||||
private data class SenderKeyLocator(val address: SignalProtocolAddress, val distributionId: UUID)
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
package org.signal.util
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
import org.signal.libsignal.internal.Native
|
||||
import org.signal.libsignal.internal.NativeHandleGuard
|
||||
import org.signal.libsignal.metadata.certificate.CertificateValidator
|
||||
import org.signal.libsignal.metadata.certificate.SenderCertificate
|
||||
import org.signal.libsignal.metadata.certificate.ServerCertificate
|
||||
import org.signal.libsignal.protocol.SessionBuilder
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress
|
||||
import org.signal.libsignal.protocol.ecc.Curve
|
||||
import org.signal.libsignal.protocol.ecc.ECKeyPair
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey
|
||||
import org.signal.libsignal.protocol.state.PreKeyBundle
|
||||
import org.signal.libsignal.protocol.state.PreKeyRecord
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
|
||||
import org.whispersystems.signalservice.api.SignalSessionLock
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeContent
|
||||
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import org.whispersystems.signalservice.internal.util.Util
|
||||
import org.whispersystems.util.Base64
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* An in-memory signal client that can encrypt and decrypt messages.
|
||||
*
|
||||
* Has a single prekey bundle that can be used to initialize a session with another client.
|
||||
*/
|
||||
class SignalClient {
|
||||
companion object {
|
||||
private val trustRoot: ECKeyPair = Curve.generateKeyPair()
|
||||
}
|
||||
|
||||
private val serviceId: ServiceId = ServiceId.from(UUID.randomUUID())
|
||||
|
||||
private val store: SignalServiceAccountDataStore = InMemorySignalServiceAccountDataStore()
|
||||
|
||||
private val preKeyBundle: PreKeyBundle = let {
|
||||
val preKeyRecord = PreKeyRecord(1, Curve.generateKeyPair())
|
||||
val signedPreKeyPair = Curve.generateKeyPair()
|
||||
val signedPreKeySignature = Curve.calculateSignature(store.identityKeyPair.privateKey, signedPreKeyPair.publicKey.serialize())
|
||||
|
||||
store.storePreKey(1, preKeyRecord)
|
||||
store.storeSignedPreKey(1, SignedPreKeyRecord(1, System.currentTimeMillis(), signedPreKeyPair, signedPreKeySignature))
|
||||
|
||||
PreKeyBundle(1, 1, 1, preKeyRecord.keyPair.publicKey, 1, signedPreKeyPair.publicKey, signedPreKeySignature, store.identityKeyPair.publicKey)
|
||||
}
|
||||
|
||||
private val unidentifiedAccessKey: ByteArray = Util.getSecretBytes(32)
|
||||
|
||||
private val senderCertificate: SenderCertificate = createCertificateFor(
|
||||
trustRoot = trustRoot,
|
||||
uuid = serviceId.uuid(),
|
||||
e164 = "+${Random.nextLong(1111111111L, 9999999999L)}",
|
||||
deviceId = 1,
|
||||
identityKey = store.identityKeyPair.publicKey.publicKey,
|
||||
expires = Long.MAX_VALUE
|
||||
)
|
||||
|
||||
private val cipher = SignalServiceCipher(SignalServiceAddress(serviceId), 1, store, TestSessionLock(), CertificateValidator(trustRoot.publicKey))
|
||||
|
||||
/**
|
||||
* Sets up sessions using the [to] client's [preKeyBundle]. Note that you can only initialize a client once
|
||||
* since we currently only make a single prekey bundle.
|
||||
*/
|
||||
fun initializeSession(to: SignalClient) {
|
||||
val address = SignalProtocolAddress(to.serviceId.toString(), 1)
|
||||
SessionBuilder(store, address).process(to.preKeyBundle)
|
||||
}
|
||||
|
||||
fun encryptUnsealedSender(to: SignalClient): SignalServiceProtos.Envelope {
|
||||
val sentTimestamp = System.currentTimeMillis()
|
||||
|
||||
val message = SignalServiceProtos.DataMessage.newBuilder()
|
||||
.setBody("Test Message")
|
||||
.setTimestamp(sentTimestamp)
|
||||
.build()
|
||||
|
||||
val content = SignalServiceProtos.Content.newBuilder()
|
||||
.setDataMessage(message)
|
||||
.build()
|
||||
|
||||
val outgoingPushMessage: OutgoingPushMessage = cipher.encrypt(
|
||||
SignalProtocolAddress(to.serviceId.toString(), 1),
|
||||
Optional.empty(),
|
||||
EnvelopeContent.encrypted(content, ContentHint.RESENDABLE, Optional.empty())
|
||||
)
|
||||
|
||||
val encryptedContent: ByteArray = Base64.decode(outgoingPushMessage.content)
|
||||
|
||||
return SignalServiceProtos.Envelope.newBuilder()
|
||||
.setSourceUuid(serviceId.toString())
|
||||
.setSourceDevice(1)
|
||||
.setDestinationUuid(to.serviceId.toString())
|
||||
.setTimestamp(sentTimestamp)
|
||||
.setServerTimestamp(sentTimestamp)
|
||||
.setServerGuid(UUID.randomUUID().toString())
|
||||
.setType(SignalServiceProtos.Envelope.Type.valueOf(outgoingPushMessage.type))
|
||||
.setUrgent(true)
|
||||
.setContent(ByteString.copyFrom(encryptedContent))
|
||||
.build()
|
||||
}
|
||||
|
||||
fun encryptSealedSender(to: SignalClient): SignalServiceProtos.Envelope {
|
||||
val sentTimestamp = System.currentTimeMillis()
|
||||
|
||||
val message = SignalServiceProtos.DataMessage.newBuilder()
|
||||
.setBody("Test Message")
|
||||
.setTimestamp(sentTimestamp)
|
||||
.build()
|
||||
|
||||
val content = SignalServiceProtos.Content.newBuilder()
|
||||
.setDataMessage(message)
|
||||
.build()
|
||||
|
||||
val outgoingPushMessage: OutgoingPushMessage = cipher.encrypt(
|
||||
SignalProtocolAddress(to.serviceId.toString(), 1),
|
||||
Optional.of(UnidentifiedAccess(to.unidentifiedAccessKey, senderCertificate.serialized)),
|
||||
EnvelopeContent.encrypted(content, ContentHint.RESENDABLE, Optional.empty())
|
||||
)
|
||||
|
||||
val encryptedContent: ByteArray = Base64.decode(outgoingPushMessage.content)
|
||||
|
||||
return SignalServiceProtos.Envelope.newBuilder()
|
||||
.setSourceUuid(serviceId.toString())
|
||||
.setSourceDevice(1)
|
||||
.setDestinationUuid(to.serviceId.toString())
|
||||
.setTimestamp(sentTimestamp)
|
||||
.setServerTimestamp(sentTimestamp)
|
||||
.setServerGuid(UUID.randomUUID().toString())
|
||||
.setType(SignalServiceProtos.Envelope.Type.valueOf(outgoingPushMessage.type))
|
||||
.setUrgent(true)
|
||||
.setContent(ByteString.copyFrom(encryptedContent))
|
||||
.build()
|
||||
}
|
||||
|
||||
fun decryptMessage(envelope: SignalServiceProtos.Envelope) {
|
||||
cipher.decrypt(envelope, System.currentTimeMillis())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCertificateFor(trustRoot: ECKeyPair, uuid: UUID, e164: String, deviceId: Int, identityKey: ECPublicKey, expires: Long): SenderCertificate {
|
||||
val serverKey: ECKeyPair = Curve.generateKeyPair()
|
||||
NativeHandleGuard(serverKey.publicKey).use { serverPublicGuard ->
|
||||
NativeHandleGuard(trustRoot.privateKey).use { trustRootPrivateGuard ->
|
||||
val serverCertificate = ServerCertificate(Native.ServerCertificate_New(1, serverPublicGuard.nativeHandle(), trustRootPrivateGuard.nativeHandle()))
|
||||
NativeHandleGuard(identityKey).use { identityGuard ->
|
||||
NativeHandleGuard(serverCertificate).use { serverCertificateGuard ->
|
||||
NativeHandleGuard(serverKey.privateKey).use { serverPrivateGuard ->
|
||||
return SenderCertificate(Native.SenderCertificate_New(uuid.toString(), e164, deviceId, identityGuard.nativeHandle(), expires, serverCertificateGuard.nativeHandle(), serverPrivateGuard.nativeHandle()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TestSessionLock : SignalSessionLock {
|
||||
val lock = ReentrantLock()
|
||||
|
||||
override fun acquire(): SignalSessionLock.Lock {
|
||||
lock.lock()
|
||||
return SignalSessionLock.Lock { lock.unlock() }
|
||||
}
|
||||
}
|
2
microbenchmark/src/main/AndroidManifest.xml
Normal file
2
microbenchmark/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
|
@ -88,3 +88,4 @@ project(':qr-app').projectDir = file('qr/app')
|
|||
rootProject.name='Signal'
|
||||
|
||||
apply from: 'dependencies.gradle'
|
||||
include ':microbenchmark'
|
||||
|
|
Loading…
Add table
Reference in a new issue