diff --git a/app/build.gradle b/app/build.gradle
index 17f56adf57..3e88bb05e4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -303,7 +303,6 @@ dependencies {
implementation 'androidx.media3:media3-exoplayer:1.4.0'
implementation 'androidx.media3:media3-ui:1.4.0'
implementation 'org.conscrypt:conscrypt-android:2.5.2'
- implementation 'org.signal:aesgcmprovider:0.0.3'
implementation 'io.github.webrtc-sdk:android:125.6422.06.1'
implementation "me.leolin:ShortcutBadger:1.1.16"
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ff8031044f..25dbdcf341 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -85,7 +85,6 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
- android:networkSecurityConfig="@xml/network_security_configuration"
android:supportsRtl="true"
android:theme="@style/Theme.Session.DayNight"
tools:replace="android:allowBackup,android:label" >
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
index 99debd596c..9bc710a7bc 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
@@ -64,7 +64,6 @@
import org.session.libsignal.utilities.JsonUtil;
import org.session.libsignal.utilities.Log;
import org.session.libsignal.utilities.ThreadUtils;
-import org.signal.aesgcmprovider.AesGcmProvider;
import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.configs.ConfigUploader;
import org.thoughtcrime.securesms.database.EmojiSearchDatabase;
@@ -119,6 +118,8 @@
import dagger.hilt.android.HiltAndroidApp;
import kotlin.Deprecated;
import kotlin.Unit;
+import kotlinx.coroutines.Dispatchers;
+import kotlinx.coroutines.DispatchersKt;
import network.loki.messenger.BuildConfig;
import network.loki.messenger.R;
@@ -394,22 +395,22 @@ public boolean isAppVisible() {
// Loki
private void initializeSecurityProvider() {
- try {
- Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
- } catch (ClassNotFoundException e) {
- Log.e(TAG, "Failed to find AesGcmCipher class");
- throw new ProviderInitializationException();
- }
-
- int aesPosition = Security.insertProviderAt(new AesGcmProvider(), 1);
- Log.i(TAG, "Installed AesGcmProvider: " + aesPosition);
-
- if (aesPosition < 0) {
- Log.e(TAG, "Failed to install AesGcmProvider()");
- throw new ProviderInitializationException();
- }
-
- int conscryptPosition = Security.insertProviderAt(Conscrypt.newProvider(), 2);
+// try {
+// Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
+// } catch (ClassNotFoundException e) {
+// Log.e(TAG, "Failed to find AesGcmCipher class");
+// throw new ProviderInitializationException();
+// }
+//
+// int aesPosition = Security.insertProviderAt(new AesGcmProvider(), 1);
+// Log.i(TAG, "Installed AesGcmProvider: " + aesPosition);
+//
+// if (aesPosition < 0) {
+// Log.e(TAG, "Failed to install AesGcmProvider()");
+// throw new ProviderInitializationException();
+// }
+
+ int conscryptPosition = Security.insertProviderAt(Conscrypt.newProvider(), 1);
Log.i(TAG, "Installed Conscrypt provider: " + conscryptPosition);
if (conscryptPosition < 0) {
@@ -549,4 +550,7 @@ public void wakeUpDeviceAndDismissKeyguardIfRequired() {
}
}
+ static {
+ System.setProperty(DispatchersKt.IO_PARALLELISM_PROPERTY_NAME, "10");
+ }
}
diff --git a/app/src/main/res/xml/network_security_configuration.xml b/app/src/main/res/xml/network_security_configuration.xml
deleted file mode 100644
index b0392ade39..0000000000
--- a/app/src/main/res/xml/network_security_configuration.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
- 127.0.0.1
-
-
- seed1.getsession.org
-
-
-
-
-
- seed2.getsession.org
-
-
-
-
-
- seed3.getsession.org
-
-
-
-
-
- public.loki.foundation
-
-
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 3d92c73210..7b97feabfa 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -19,6 +19,7 @@ org.gradle.jvmargs=-Xmx3096M -Dkotlin.daemon.jvm.options\="-Xmx3096M"
gradlePluginVersion=8.5.2
googleServicesVersion=4.3.12
kotlinVersion=2.0.0
+ktorVersion=3.0.3
kspVersion=2.0.0-1.0.23
navVersion=2.8.0-beta05
android.useAndroidX=true
diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt
index f1634d8e2e..905cbf9c90 100644
--- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt
+++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt
@@ -17,6 +17,7 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select
+import kotlinx.coroutines.selects.onTimeout
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.all
import nl.komponents.kovenant.functional.bind
@@ -81,7 +82,7 @@ object SnodeAPI {
private const val minimumSnodePoolCount = 12
private const val minimumSwarmSnodeCount = 3
// Use port 4433 to enforce pinned certificates
- private val seedNodePort = 4443
+ private val seedNodePort = 443
private val seedNodePool = if (SnodeModule.shared.useTestNet) setOf(
"http://public.loki.foundation:38157"
diff --git a/libsession/src/main/java/org/session/libsession/utilities/Util.kt b/libsession/src/main/java/org/session/libsession/utilities/Util.kt
index e0c47c34c7..493dce835f 100644
--- a/libsession/src/main/java/org/session/libsession/utilities/Util.kt
+++ b/libsession/src/main/java/org/session/libsession/utilities/Util.kt
@@ -45,7 +45,7 @@ object Util {
@JvmStatic
@Throws(IOException::class)
fun copy(`in`: InputStream, out: OutputStream?): Long {
- val buffer = ByteArray(8192)
+ val buffer = ByteArray(24560)
var read: Int
var total: Long = 0
while (`in`.read(buffer).also { read = it } != -1) {
diff --git a/libsignal/build.gradle b/libsignal/build.gradle
index 9fac9541d9..1c2e589294 100644
--- a/libsignal/build.gradle
+++ b/libsignal/build.gradle
@@ -30,6 +30,9 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
implementation "nl.komponents.kovenant:kovenant:$kovenantVersion"
+
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
+
testImplementation "junit:junit:$junitVersion"
testImplementation "org.assertj:assertj-core:3.11.1"
testImplementation "org.conscrypt:conscrypt-openjdk-uber:2.0.0"
diff --git a/libsignal/src/main/java/org/session/libsignal/streams/ProfileCipherInputStream.java b/libsignal/src/main/java/org/session/libsignal/streams/ProfileCipherInputStream.java
index 19996c17cb..a20f4c17f3 100644
--- a/libsignal/src/main/java/org/session/libsignal/streams/ProfileCipherInputStream.java
+++ b/libsignal/src/main/java/org/session/libsignal/streams/ProfileCipherInputStream.java
@@ -19,6 +19,7 @@
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
+//TODO: Rewrite this class so that it doesn't rely on specific cipher length assumptions
public class ProfileCipherInputStream extends FilterInputStream {
private final Cipher cipher;
@@ -35,12 +36,9 @@ public ProfileCipherInputStream(InputStream in, byte[] key) throws IOException {
Util.readFully(in, nonce);
this.cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, nonce));
- } catch (NoSuchAlgorithmException e) {
- throw new AssertionError(e);
- } catch (NoSuchPaddingException e) {
- throw new AssertionError(e);
- } catch (InvalidAlgorithmParameterException e) {
- throw new AssertionError(e);
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException |
+ InvalidAlgorithmParameterException e) {
+ throw new RuntimeException(e);
} catch (InvalidKeyException e) {
throw new IOException(e);
}
@@ -67,21 +65,21 @@ public int read(byte[] output, int outputOffset, int outputLength) throws IOExce
synchronized (CIPHER_LOCK) {
if (read == -1) {
if (cipher.getOutputSize(0) > outputLength) {
- throw new AssertionError("Need: " + cipher.getOutputSize(0) + " but only have: " + outputLength);
+ throw new RuntimeException("Need: " + cipher.getOutputSize(0) + " but only have: " + outputLength);
}
finished = true;
return cipher.doFinal(output, outputOffset);
} else {
if (cipher.getOutputSize(read) > outputLength) {
- throw new AssertionError("Need: " + cipher.getOutputSize(read) + " but only have: " + outputLength);
+ throw new RuntimeException("Need: " + cipher.getOutputSize(read) + " but only have: " + outputLength);
}
return cipher.update(ciphertext, 0, read, output, outputOffset);
}
}
} catch (IllegalBlockSizeException | ShortBufferException e) {
- throw new AssertionError(e);
+ throw new RuntimeException(e);
} catch (BadPaddingException e) {
throw new IOException(e);
}
diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/HTTP.kt b/libsignal/src/main/java/org/session/libsignal/utilities/HTTP.kt
index fc29d07916..a459fa2921 100644
--- a/libsignal/src/main/java/org/session/libsignal/utilities/HTTP.kt
+++ b/libsignal/src/main/java/org/session/libsignal/utilities/HTTP.kt
@@ -1,72 +1,50 @@
package org.session.libsignal.utilities
+import android.annotation.SuppressLint
+import android.net.http.X509TrustManagerExtensions
import android.util.Log
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import okhttp3.Call
-import okhttp3.MediaType.Companion.toMediaType
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.RequestBody
-import okhttp3.Response
-import org.session.libsignal.utilities.Util.SECURE_RANDOM
+import io.ktor.client.HttpClient
+import io.ktor.client.engine.cio.CIO
+import io.ktor.client.plugins.timeout
+import io.ktor.client.request.headers
+import io.ktor.client.request.request
+import io.ktor.client.request.setBody
+import io.ktor.client.statement.bodyAsBytes
+import io.ktor.http.ContentType
+import io.ktor.http.HttpMethod
+import io.ktor.http.HttpStatusCode
+import io.ktor.http.contentType
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
-import javax.net.ssl.SSLContext
import javax.net.ssl.X509TrustManager
object HTTP {
var isConnectedToNetwork: (() -> Boolean) = { false }
- private val seedNodeConnection by lazy {
-
- OkHttpClient().newBuilder()
- .callTimeout(timeout, TimeUnit.SECONDS)
- .connectTimeout(timeout, TimeUnit.SECONDS)
- .readTimeout(timeout, TimeUnit.SECONDS)
- .writeTimeout(timeout, TimeUnit.SECONDS)
- .build()
+ private val seedNodeClient: HttpClient by lazy {
+ HttpClient(CIO)
}
- private val defaultConnection by lazy {
- // Snode to snode communication uses self-signed certificates but clients can safely ignore this
- val trustManager = object : X509TrustManager {
+ private val serviceNodeClient: HttpClient by lazy {
+ HttpClient(CIO) {
+ engine {
+ https {
+ @SuppressLint("CustomX509TrustManager")
+ trustManager = object : X509TrustManager {
+ @SuppressLint("TrustAllX509TrustManager")
+ override fun checkClientTrusted(chain: Array?, authorizationType: String?) { }
- override fun checkClientTrusted(chain: Array?, authorizationType: String?) { }
- override fun checkServerTrusted(chain: Array?, authorizationType: String?) { }
- override fun getAcceptedIssuers(): Array { return arrayOf() }
- }
- val sslContext = SSLContext.getInstance("SSL")
- sslContext.init(null, arrayOf( trustManager ), SECURE_RANDOM)
- OkHttpClient().newBuilder()
- .sslSocketFactory(sslContext.socketFactory, trustManager)
- .hostnameVerifier { _, _ -> true }
- .callTimeout(timeout, TimeUnit.SECONDS)
- .connectTimeout(timeout, TimeUnit.SECONDS)
- .readTimeout(timeout, TimeUnit.SECONDS)
- .writeTimeout(timeout, TimeUnit.SECONDS)
- .build()
- }
+ @SuppressLint("TrustAllX509TrustManager")
+ override fun checkServerTrusted(chain: Array?, authorizationType: String?) { }
- private fun getDefaultConnection(timeout: Long): OkHttpClient {
- // Snode to snode communication uses self-signed certificates but clients can safely ignore this
- val trustManager = object : X509TrustManager {
+ override fun getAcceptedIssuers(): Array { return arrayOf() }
+ }
+ }
- override fun checkClientTrusted(chain: Array?, authorizationType: String?) { }
- override fun checkServerTrusted(chain: Array?, authorizationType: String?) { }
- override fun getAcceptedIssuers(): Array { return arrayOf() }
+ requestTimeout = TimeUnit.SECONDS.toMillis(timeout)
+ }
}
- val sslContext = SSLContext.getInstance("SSL")
- sslContext.init(null, arrayOf( trustManager ), SECURE_RANDOM)
- return OkHttpClient().newBuilder()
- .sslSocketFactory(sslContext.socketFactory, trustManager)
- .hostnameVerifier { _, _ -> true }
- .callTimeout(timeout, TimeUnit.SECONDS)
- .connectTimeout(timeout, TimeUnit.SECONDS)
- .readTimeout(timeout, TimeUnit.SECONDS)
- .writeTimeout(timeout, TimeUnit.SECONDS)
- .build()
}
private const val timeout: Long = 120
@@ -105,41 +83,41 @@ object HTTP {
* Sync. Don't call from the main thread.
*/
suspend fun execute(verb: Verb, url: String, body: ByteArray?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
- val request = Request.Builder().url(url)
- .removeHeader("User-Agent").addHeader("User-Agent", "WhatsApp") // Set a fake value
- .removeHeader("Accept-Language").addHeader("Accept-Language", "en-us") // Set a fake value
- when (verb) {
- Verb.GET -> request.get()
- Verb.PUT, Verb.POST -> {
- if (body == null) { throw Exception("Invalid request body.") }
- val contentType = "application/json; charset=utf-8".toMediaType()
- @Suppress("NAME_SHADOWING") val body = RequestBody.create(contentType, body)
- if (verb == Verb.PUT) request.put(body) else request.post(body)
- }
- Verb.DELETE -> request.delete()
- }
- return try {
- when {
- // Custom timeout
- timeout != HTTP.timeout -> {
- if (useSeedNodeConnection) {
- throw IllegalStateException("Setting a custom timeout is only allowed for requests to snodes.")
- }
- getDefaultConnection(timeout)
+ val client = if (useSeedNodeConnection) seedNodeClient else serviceNodeClient
+
+ try {
+ val response = client.request(url) {
+ method = HttpMethod.parse(verb.rawValue)
+
+ headers {
+ remove("User-Agent")
+ remove("Accept-Language")
+
+ append("User-Agent", "WhatsApp")
+ append("Accept-Language", "en-us")
}
- useSeedNodeConnection -> seedNodeConnection
- else -> defaultConnection
- }.newCall(request.build()).await().use { response ->
- when (val statusCode = response.code) {
- 200 -> response.body!!.bytes()
- else -> {
- Log.d("Loki", "${verb.rawValue} request to $url failed with status code: $statusCode.")
- throw HTTPRequestFailedException(statusCode, null)
- }
+
+ if (verb == Verb.PUT || verb == Verb.POST) {
+ check(body != null) { "Invalid request body." }
+ contentType(ContentType.Application.Json)
+ setBody(body)
+ }
+
+ timeout {
+ requestTimeoutMillis = TimeUnit.SECONDS.toMillis(timeout)
+ }
+ }
+
+ when (val code = response.status) {
+ HttpStatusCode.OK -> return response.bodyAsBytes()
+ else -> {
+ Log.d("Loki", "${verb.rawValue} request to $url failed with status code: $code.")
+ throw HTTPRequestFailedException(code.value, null)
}
}
+
} catch (exception: Exception) {
- Log.d("Loki", "${verb.rawValue} request to $url failed due to error: ${exception.localizedMessage}.")
+ Log.d("Loki", "${verb.rawValue} request to $url failed due to error: ${exception.localizedMessage}.", exception)
if (!isConnectedToNetwork()) { throw HTTPNoNetworkException() }
@@ -147,13 +125,4 @@ object HTTP {
throw HTTPRequestFailedException(0, null, "HTTP request failed due to: ${exception.message}")
}
}
-
- @Suppress("OPT_IN_USAGE")
- private val httpCallDispatcher = Dispatchers.IO.limitedParallelism(15)
-
- private suspend fun Call.await(): Response {
- return withContext(httpCallDispatcher) {
- execute()
- }
- }
}