From 6d4906dfa868678cac07ee46e476a18165f33c57 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 6 Apr 2023 10:04:37 -0400 Subject: [PATCH] Add microbenchmarks for message decryption. --- benchmark/build.gradle.kts | 2 +- .../plugins/src/main/java/ktlint.gradle.kts | 8 + .../src/main/java/signal-library.gradle.kts | 7 +- .../main/java/signal-sample-app.gradle.kts | 7 +- build.gradle | 1 + dependencies.gradle | 1 + gradle/verification-metadata.xml | 58 ++++++ microbenchmark/.gitignore | 1 + microbenchmark/benchmark-proguard-rules.pro | 37 ++++ microbenchmark/build.gradle.kts | 63 +++++++ .../src/androidTest/AndroidManifest.xml | 15 ++ .../microbenchmark/ProtocolBenchmarks.kt | 89 +++++++++ .../InMemorySignalServiceAccountDataStore.kt | 157 ++++++++++++++++ .../java/org/signal/util/SignalClient.kt | 175 ++++++++++++++++++ microbenchmark/src/main/AndroidManifest.xml | 2 + settings.gradle | 1 + 16 files changed, 611 insertions(+), 13 deletions(-) create mode 100644 build-logic/plugins/src/main/java/ktlint.gradle.kts create mode 100644 microbenchmark/.gitignore create mode 100644 microbenchmark/benchmark-proguard-rules.pro create mode 100644 microbenchmark/build.gradle.kts create mode 100644 microbenchmark/src/androidTest/AndroidManifest.xml create mode 100644 microbenchmark/src/androidTest/java/org/signal/microbenchmark/ProtocolBenchmarks.kt create mode 100644 microbenchmark/src/androidTest/java/org/signal/util/InMemorySignalServiceAccountDataStore.kt create mode 100644 microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt create mode 100644 microbenchmark/src/main/AndroidManifest.xml diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts index b1462135b5..22b2f84ee3 100644 --- a/benchmark/build.gradle.kts +++ b/benchmark/build.gradle.kts @@ -55,7 +55,7 @@ android { testOptions { managedDevices { devices { - create ("api31", ManagedVirtualDevice::class) { + create("api31", ManagedVirtualDevice::class) { device = "Pixel 6" apiLevel = 31 systemImageSource = "aosp" diff --git a/build-logic/plugins/src/main/java/ktlint.gradle.kts b/build-logic/plugins/src/main/java/ktlint.gradle.kts new file mode 100644 index 0000000000..228c78f6dd --- /dev/null +++ b/build-logic/plugins/src/main/java/ktlint.gradle.kts @@ -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") +} diff --git a/build-logic/plugins/src/main/java/signal-library.gradle.kts b/build-logic/plugins/src/main/java/signal-library.gradle.kts index 36ac68df48..f88bc83ca8 100644 --- a/build-logic/plugins/src/main/java/signal-library.gradle.kts +++ b/build-logic/plugins/src/main/java/signal-library.gradle.kts @@ -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")) diff --git a/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts b/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts index 2990fe16c0..556432f23c 100644 --- a/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts +++ b/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts @@ -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) diff --git a/build.gradle b/build.gradle index e0df44805c..a825500cdb 100644 --- a/build.gradle +++ b/build.gradle @@ -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' } } diff --git a/dependencies.gradle b/dependencies.gradle index 450f586062..1a463e27a9 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -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 { diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index b3149f67a4..7a22980a78 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -102,6 +102,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -180,6 +185,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -204,6 +217,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + @@ -1184,6 +1213,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1267,6 +1301,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + @@ -3404,6 +3441,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3481,6 +3526,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -4506,6 +4559,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + diff --git a/microbenchmark/.gitignore b/microbenchmark/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/microbenchmark/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/microbenchmark/benchmark-proguard-rules.pro b/microbenchmark/benchmark-proguard-rules.pro new file mode 100644 index 0000000000..e4061d2224 --- /dev/null +++ b/microbenchmark/benchmark-proguard-rules.pro @@ -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 * \ No newline at end of file diff --git a/microbenchmark/build.gradle.kts b/microbenchmark/build.gradle.kts new file mode 100644 index 0000000000..b2ae0d58f6 --- /dev/null +++ b/microbenchmark/build.gradle.kts @@ -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) +} diff --git a/microbenchmark/src/androidTest/AndroidManifest.xml b/microbenchmark/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000000..405595ca6b --- /dev/null +++ b/microbenchmark/src/androidTest/AndroidManifest.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/microbenchmark/src/androidTest/java/org/signal/microbenchmark/ProtocolBenchmarks.kt b/microbenchmark/src/androidTest/java/org/signal/microbenchmark/ProtocolBenchmarks.kt new file mode 100644 index 0000000000..3b96c52189 --- /dev/null +++ b/microbenchmark/src/androidTest/java/org/signal/microbenchmark/ProtocolBenchmarks.kt @@ -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 { + 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 + } +} diff --git a/microbenchmark/src/androidTest/java/org/signal/util/InMemorySignalServiceAccountDataStore.kt b/microbenchmark/src/androidTest/java/org/signal/util/InMemorySignalServiceAccountDataStore.kt new file mode 100644 index 0000000000..9beec9f1fd --- /dev/null +++ b/microbenchmark/src/androidTest/java/org/signal/util/InMemorySignalServiceAccountDataStore.kt @@ -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 = mutableMapOf() + private val oneTimePreKeys: MutableMap = mutableMapOf() + private val signedPreKeys: MutableMap = mutableMapOf() + private var sessions: MutableMap = mutableMapOf() + private val senderKeys: MutableMap = 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): List { + return addresses.map { sessions[it]!! } + } + + override fun getSubDeviceSessions(name: String): List { + 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 { + 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): Set { + return sessions + .filter { it.key.name in addressNames } + .filter { it.value.isValid() } + .map { it.key } + .toSet() + } + + override fun getSenderKeySharedWith(distributionId: DistributionId): Set { + error("Not used") + } + + override fun markSenderKeySharedWith(distributionId: DistributionId, addresses: Collection) { + // Not used + } + + override fun clearSenderKeySharedWith(addresses: Collection) { + // 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) +} diff --git a/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt b/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt new file mode 100644 index 0000000000..cf13a291ff --- /dev/null +++ b/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt @@ -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() } + } +} diff --git a/microbenchmark/src/main/AndroidManifest.xml b/microbenchmark/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..568741e54f --- /dev/null +++ b/microbenchmark/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 59aa5e625f..feb7a38ab9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -88,3 +88,4 @@ project(':qr-app').projectDir = file('qr/app') rootProject.name='Signal' apply from: 'dependencies.gradle' +include ':microbenchmark'