From 311c36b7c0d0a8a183b2f7037014e2323384b396 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 8 May 2024 13:16:10 +0300 Subject: [PATCH] Builtin ssl certificates for old devices --- app/build.gradle | 7 ++- .../koitharu/kotatsu/core/util/ext/Debug.kt | 9 +++ .../kotatsu/core/util/ext/DebugExt.kt | 3 - app/src/main/assets/isrgrootx1.pem | 31 +++++++++ .../kotatsu/core/network/NetworkModule.kt | 8 ++- .../kotatsu/core/network/SSLBypass.kt | 30 --------- .../koitharu/kotatsu/core/network/SSLUtils.kt | 63 +++++++++++++++++++ .../core/parser/RemoteMangaRepository.kt | 1 - .../kotatsu/core/prefs/AppSettings.kt | 2 +- app/src/main/res/xml/pref_sources.xml | 2 +- .../koitharu/kotatsu/core/util/ext/Debug.kt | 2 + build.gradle | 2 +- 12 files changed, 119 insertions(+), 41 deletions(-) create mode 100644 app/src/debug/kotlin/org/koitharu/kotatsu/core/util/ext/Debug.kt delete mode 100644 app/src/debug/kotlin/org/koitharu/kotatsu/core/util/ext/DebugExt.kt create mode 100644 app/src/main/assets/isrgrootx1.pem delete mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/network/SSLBypass.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/network/SSLUtils.kt diff --git a/app/build.gradle b/app/build.gradle index 86b3aff91..b1173bb81 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdk = 21 targetSdk = 34 - versionCode = 637 - versionName = '7.0-b3' + versionCode = 638 + versionName = '7.0-rc1' generatedDensities = [] testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' ksp { @@ -82,7 +82,7 @@ afterEvaluate { } dependencies { //noinspection GradleDependency - implementation('com.github.KotatsuApp:kotatsu-parsers:a245574dee') { + implementation('com.github.KotatsuApp:kotatsu-parsers:33b00fe65f') { exclude group: 'org.json', module: 'json' } @@ -121,6 +121,7 @@ dependencies { ksp 'androidx.room:room-compiler:2.6.1' implementation 'com.squareup.okhttp3:okhttp:4.12.0' + implementation 'com.squareup.okhttp3:okhttp-tls:4.12.0' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.12.0' implementation 'com.squareup.okio:okio:3.9.0' diff --git a/app/src/debug/kotlin/org/koitharu/kotatsu/core/util/ext/Debug.kt b/app/src/debug/kotlin/org/koitharu/kotatsu/core/util/ext/Debug.kt new file mode 100644 index 000000000..9e6032d7c --- /dev/null +++ b/app/src/debug/kotlin/org/koitharu/kotatsu/core/util/ext/Debug.kt @@ -0,0 +1,9 @@ +package org.koitharu.kotatsu.core.util.ext + +import android.os.Looper + +fun Throwable.printStackTraceDebug() = printStackTrace() + +fun assertNotInMainThread() = check(Looper.myLooper() != Looper.getMainLooper()) { + "Calling this from the main thread is prohibited" +} diff --git a/app/src/debug/kotlin/org/koitharu/kotatsu/core/util/ext/DebugExt.kt b/app/src/debug/kotlin/org/koitharu/kotatsu/core/util/ext/DebugExt.kt deleted file mode 100644 index 62af20cbc..000000000 --- a/app/src/debug/kotlin/org/koitharu/kotatsu/core/util/ext/DebugExt.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.koitharu.kotatsu.core.util.ext - -fun Throwable.printStackTraceDebug() = printStackTrace() diff --git a/app/src/main/assets/isrgrootx1.pem b/app/src/main/assets/isrgrootx1.pem new file mode 100644 index 000000000..b85c8037f --- /dev/null +++ b/app/src/main/assets/isrgrootx1.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/NetworkModule.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/NetworkModule.kt index e9e6a6dc6..8e0d80ea8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/NetworkModule.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/NetworkModule.kt @@ -16,8 +16,10 @@ import org.koitharu.kotatsu.core.network.cookies.AndroidCookieJar import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar import org.koitharu.kotatsu.core.network.cookies.PreferencesCookieJar import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.util.ext.assertNotInMainThread import org.koitharu.kotatsu.local.data.LocalStorageManager import java.util.concurrent.TimeUnit +import javax.inject.Provider import javax.inject.Singleton @Module @@ -50,10 +52,12 @@ interface NetworkModule { @Singleton @BaseHttpClient fun provideBaseHttpClient( + @ApplicationContext contextProvider: Provider, cache: Cache, cookieJar: CookieJar, settings: AppSettings, ): OkHttpClient = OkHttpClient.Builder().apply { + assertNotInMainThread() connectTimeout(20, TimeUnit.SECONDS) readTimeout(60, TimeUnit.SECONDS) writeTimeout(20, TimeUnit.SECONDS) @@ -62,7 +66,9 @@ interface NetworkModule { proxyAuthenticator(ProxyAuthenticator(settings)) dns(DoHManager(cache, settings)) if (settings.isSSLBypassEnabled) { - bypassSSLErrors() + disableCertificateVerification() + } else { + installExtraCertsificates(contextProvider.get()) } cache(cache) addInterceptor(GZipInterceptor()) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/SSLBypass.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/SSLBypass.kt deleted file mode 100644 index d5ef6fd59..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/SSLBypass.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.koitharu.kotatsu.core.network - -import android.annotation.SuppressLint -import okhttp3.OkHttpClient -import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug -import java.security.SecureRandom -import java.security.cert.X509Certificate -import javax.net.ssl.SSLContext -import javax.net.ssl.SSLSocketFactory -import javax.net.ssl.X509TrustManager - -@SuppressLint("CustomX509TrustManager") -fun OkHttpClient.Builder.bypassSSLErrors() = also { builder -> - runCatching { - val trustAllCerts = object : X509TrustManager { - override fun checkClientTrusted(chain: Array, authType: String) = Unit - - override fun checkServerTrusted(chain: Array, authType: String) = Unit - - override fun getAcceptedIssuers(): Array = emptyArray() - } - val sslContext = SSLContext.getInstance("SSL") - sslContext.init(null, arrayOf(trustAllCerts), SecureRandom()) - val sslSocketFactory: SSLSocketFactory = sslContext.socketFactory - builder.sslSocketFactory(sslSocketFactory, trustAllCerts) - builder.hostnameVerifier { _, _ -> true } - }.onFailure { - it.printStackTraceDebug() - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/SSLUtils.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/SSLUtils.kt new file mode 100644 index 000000000..505ebc6d3 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/SSLUtils.kt @@ -0,0 +1,63 @@ +package org.koitharu.kotatsu.core.network + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.AssetManager +import android.util.Log +import okhttp3.OkHttpClient +import okhttp3.tls.HandshakeCertificates +import org.koitharu.kotatsu.BuildConfig +import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug +import java.security.SecureRandom +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.X509TrustManager + +@SuppressLint("CustomX509TrustManager") +fun OkHttpClient.Builder.disableCertificateVerification() = also { builder -> + runCatching { + val trustAllCerts = object : X509TrustManager { + override fun checkClientTrusted(chain: Array, authType: String) = Unit + + override fun checkServerTrusted(chain: Array, authType: String) = Unit + + override fun getAcceptedIssuers(): Array = emptyArray() + } + val sslContext = SSLContext.getInstance("SSL") + sslContext.init(null, arrayOf(trustAllCerts), SecureRandom()) + val sslSocketFactory: SSLSocketFactory = sslContext.socketFactory + builder.sslSocketFactory(sslSocketFactory, trustAllCerts) + builder.hostnameVerifier { _, _ -> true } + }.onFailure { + it.printStackTraceDebug() + } +} + +fun OkHttpClient.Builder.installExtraCertsificates(context: Context) = also { builder -> + val certificatesBuilder = HandshakeCertificates.Builder() + .addPlatformTrustedCertificates() + val assets = context.assets.list("").orEmpty() + for (path in assets) { + if (path.endsWith(".pem")) { + val cert = loadCert(context, path) ?: continue + certificatesBuilder.addTrustedCertificate(cert) + } + } + val certificates = certificatesBuilder.build() + builder.sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager) +} + +private fun loadCert(context: Context, path: String): X509Certificate? = runCatching { + val cf = CertificateFactory.getInstance("X.509") + context.assets.open(path, AssetManager.ACCESS_STREAMING).use { + cf.generateCertificate(it) + } as X509Certificate +}.onFailure { e -> + e.printStackTraceDebug() +}.onSuccess { + if (BuildConfig.DEBUG) { + Log.i("ExtraCerts", "Loaded cert $path") + } +}.getOrNull() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt index 7abaad424..011d21f68 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt @@ -21,7 +21,6 @@ import org.koitharu.kotatsu.core.util.ext.processLifecycleScope import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.config.ConfigKey -import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.model.ContentRating import org.koitharu.kotatsu.parsers.model.Favicons import org.koitharu.kotatsu.parsers.model.Manga diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt index faf9c6113..516bce3e4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -281,7 +281,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { set(value) = prefs.edit { putEnumValue(KEY_SOURCES_ORDER, value) } var isSourcesGridMode: Boolean - get() = prefs.getBoolean(KEY_SOURCES_GRID, false) + get() = prefs.getBoolean(KEY_SOURCES_GRID, true) set(value) = prefs.edit { putBoolean(KEY_SOURCES_GRID, value) } val isNewSourcesTipEnabled: Boolean diff --git a/app/src/main/res/xml/pref_sources.xml b/app/src/main/res/xml/pref_sources.xml index 27a1b0171..cc2126336 100644 --- a/app/src/main/res/xml/pref_sources.xml +++ b/app/src/main/res/xml/pref_sources.xml @@ -9,7 +9,7 @@ app:useSimpleSummaryProvider="true" /> diff --git a/app/src/release/kotlin/org/koitharu/kotatsu/core/util/ext/Debug.kt b/app/src/release/kotlin/org/koitharu/kotatsu/core/util/ext/Debug.kt index 01d0a3f41..949497187 100644 --- a/app/src/release/kotlin/org/koitharu/kotatsu/core/util/ext/Debug.kt +++ b/app/src/release/kotlin/org/koitharu/kotatsu/core/util/ext/Debug.kt @@ -4,3 +4,5 @@ package org.koitharu.kotatsu.core.util.ext @Suppress("NOTHING_TO_INLINE") inline fun Throwable.printStackTraceDebug() = Unit + +fun assertNotInMainThread() = Unit diff --git a/build.gradle b/build.gradle index 78dbff684..56f9c2023 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { classpath 'com.android.tools.build:gradle:8.4.0' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23' classpath 'com.google.dagger:hilt-android-gradle-plugin:2.51.1' - classpath 'com.google.devtools.ksp:symbol-processing-gradle-plugin:1.9.23-1.0.19' + classpath 'com.google.devtools.ksp:symbol-processing-gradle-plugin:1.9.23-1.0.20' } }