From 23b1c31436b1d1cccb75b5d71e1d5c00ea9f15e9 Mon Sep 17 00:00:00 2001 From: nikk gitanes Date: Thu, 24 Oct 2024 04:48:14 +0300 Subject: [PATCH] add Conscrypt provider, SSL classes, fix notifications and change files item layout --- app/build.gradle | 3 + .../local/services/NotificationHelper.kt | 20 ++-- .../update/server/ServerUpdateFragment.kt | 6 +- .../yourok/torrserve/utils/AllTrustManager.kt | 22 ++++ .../java/ru/yourok/torrserve/utils/Http.kt | 9 +- .../java/ru/yourok/torrserve/utils/Net.kt | 43 +------- .../torrserve/utils/TlsSocketFactory.kt | 98 ++++++++++++++++++ .../main/res/drawable-hdpi/ts_icon_white.png | Bin 0 -> 1293 bytes .../main/res/drawable-xhdpi/ts_icon_white.png | Bin 0 -> 1743 bytes .../main/res/layout/torrent_files_item.xml | 16 +-- app/src/main/res/values/strings.xml | 2 +- build.gradle | 4 +- 12 files changed, 164 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/ru/yourok/torrserve/utils/AllTrustManager.kt create mode 100644 app/src/main/java/ru/yourok/torrserve/utils/TlsSocketFactory.kt create mode 100644 app/src/main/res/drawable-hdpi/ts_icon_white.png create mode 100644 app/src/main/res/drawable-xhdpi/ts_icon_white.png diff --git a/app/build.gradle b/app/build.gradle index 2ce0fab9..55e7324d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,3 +1,5 @@ +import com.android.builder.core.BuilderConstants + plugins { id("com.android.application") id("com.google.devtools.ksp") @@ -89,6 +91,7 @@ dependencies { implementation 'com.google.firebase:firebase-crashlytics:18.2.5' // SSL implementation 'info.guardianproject.netcipher:netcipher:2.1.0' + implementation 'org.conscrypt:conscrypt-android:2.5.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1' // http client/parser def jsoupVersion = '1.16.1' // 1.16.2, 1.17.1, 1.17.2 thorow java.lang.VerifyError on api19 diff --git a/app/src/main/java/ru/yourok/torrserve/server/local/services/NotificationHelper.kt b/app/src/main/java/ru/yourok/torrserve/server/local/services/NotificationHelper.kt index bc609091..2ee6cfa0 100644 --- a/app/src/main/java/ru/yourok/torrserve/server/local/services/NotificationHelper.kt +++ b/app/src/main/java/ru/yourok/torrserve/server/local/services/NotificationHelper.kt @@ -1,5 +1,6 @@ package ru.yourok.torrserve.server.local.services +import android.annotation.SuppressLint import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent @@ -15,6 +16,7 @@ import android.os.Build import android.os.IBinder import androidx.core.app.NotificationCompat import androidx.core.app.ServiceCompat +import androidx.core.graphics.drawable.IconCompat import ru.yourok.torrserve.R import ru.yourok.torrserve.app.App import ru.yourok.torrserve.atv.Utils @@ -95,6 +97,7 @@ class NotificationTS : Service() { return mBinder } + @SuppressLint("InlinedApi") private fun createNotification() { synchronized(lock) { val exitIntent = Intent(this, TorrService::class.java) @@ -120,27 +123,30 @@ class NotificationTS : Service() { .createNotificationChannel(channel) } val accessibilityNote = if (Accessibility.isEnabledService(App.context)) this.getText(R.string.accessibility_note) else "" - if (builder == null) + if (builder == null) { builder = NotificationCompat.Builder(this, channelId) - .setSmallIcon(R.drawable.ts_icon) + .setContentTitle(getString(R.string.app_name)) .setContentText(getString(R.string.stat_running)) .setAutoCancel(false) .setOngoing(true) .setContentIntent(contentPendingIntent) .setStyle(NotificationCompat.BigTextStyle().bigText(accessibilityNote)) .addAction( - android.R.drawable.ic_delete, - this.getText(R.string.exit), + android.R.drawable.ic_menu_close_clear_cancel, + this.getText(R.string.exit).toString().uppercase(), exitPendingIntent ) - else + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + builder?.setSmallIcon(IconCompat.createWithResource(this, R.drawable.ts_icon)) + else + builder?.setSmallIcon(R.drawable.ts_icon_white) + } else builder?.setStyle(NotificationCompat.BigTextStyle().bigText(accessibilityNote)) - if (Utils.isAmazonTV) builder?.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.ic_notification)) builder?.let { - ServiceCompat.startForeground(this, notificationId, it.build(), FOREGROUND_SERVICE_TYPE_SPECIAL_USE) + ServiceCompat.startForeground(this, notificationId, it.build(), FOREGROUND_SERVICE_TYPE_SPECIAL_USE) } } } diff --git a/app/src/main/java/ru/yourok/torrserve/ui/fragments/main/update/server/ServerUpdateFragment.kt b/app/src/main/java/ru/yourok/torrserve/ui/fragments/main/update/server/ServerUpdateFragment.kt index 9820cd5c..586d6b7d 100644 --- a/app/src/main/java/ru/yourok/torrserve/ui/fragments/main/update/server/ServerUpdateFragment.kt +++ b/app/src/main/java/ru/yourok/torrserve/ui/fragments/main/update/server/ServerUpdateFragment.kt @@ -51,7 +51,8 @@ class ServerUpdateFragment : TSFragment() { } } catch (e: Exception) { withContext(Dispatchers.Main) { - App.toast(App.context.getString(R.string.warn_error_download_server) + ": " + e.message) + App.toast(App.context.getString(R.string.warn_error_download_server) + ": " + e.message, true) + btn.isEnabled = true } hideProgress() } @@ -124,7 +125,8 @@ class ServerUpdateFragment : TSFragment() { } } catch (e: Exception) { withContext(Dispatchers.Main) { - App.toast(App.context.getString(R.string.error_download_ffprobe) + ": " + e.message) + App.toast(App.context.getString(R.string.error_download_ffprobe) + ": " + e.message, true) + btn.isEnabled = true } hideProgress() } diff --git a/app/src/main/java/ru/yourok/torrserve/utils/AllTrustManager.kt b/app/src/main/java/ru/yourok/torrserve/utils/AllTrustManager.kt new file mode 100644 index 00000000..8ad0ed8c --- /dev/null +++ b/app/src/main/java/ru/yourok/torrserve/utils/AllTrustManager.kt @@ -0,0 +1,22 @@ +package ru.yourok.torrserve.utils + +import android.annotation.SuppressLint +import java.security.cert.X509Certificate +import javax.net.ssl.X509TrustManager + +@SuppressLint("CustomX509TrustManager") +class AllTrustManager : X509TrustManager { + @SuppressLint("TrustAllX509TrustManager") + override fun checkClientTrusted(chain: Array, authType: String) { + // Perform no check whatsoever on the validity of the SSL certificate + } + + @SuppressLint("TrustAllX509TrustManager") + override fun checkServerTrusted(chain: Array, authType: String) { + // Perform no check whatsoever on the validity of the SSL certificate + } + + override fun getAcceptedIssuers(): Array { + return arrayOf() + } +} diff --git a/app/src/main/java/ru/yourok/torrserve/utils/Http.kt b/app/src/main/java/ru/yourok/torrserve/utils/Http.kt index beb3e6aa..8034910e 100644 --- a/app/src/main/java/ru/yourok/torrserve/utils/Http.kt +++ b/app/src/main/java/ru/yourok/torrserve/utils/Http.kt @@ -12,6 +12,7 @@ import java.net.HttpURLConnection.HTTP_OK import java.net.HttpURLConnection.HTTP_PARTIAL import java.net.HttpURLConnection.HTTP_SEE_OTHER import java.net.URL +import java.security.GeneralSecurityException import java.util.Locale import java.util.zip.GZIPInputStream import javax.net.ssl.HostnameVerifier @@ -54,7 +55,13 @@ class Http(url: Uri) { true // Just allow them all } HttpsURLConnection.setDefaultHostnameVerifier(trustAllHostnames) - HttpsURLConnection.setDefaultSSLSocketFactory(Net.insecureTlsSocketFactory()) + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + try { + // Only TLSv1.2 and TLSv1.3 protocol available and trust all certs (insecure). + HttpsURLConnection.setDefaultSSLSocketFactory(TlsSocketFactory()) + } catch (_: GeneralSecurityException) { + } } } else diff --git a/app/src/main/java/ru/yourok/torrserve/utils/Net.kt b/app/src/main/java/ru/yourok/torrserve/utils/Net.kt index 6d180993..b34c09b9 100644 --- a/app/src/main/java/ru/yourok/torrserve/utils/Net.kt +++ b/app/src/main/java/ru/yourok/torrserve/utils/Net.kt @@ -2,7 +2,6 @@ package ru.yourok.torrserve.utils import android.annotation.SuppressLint import android.net.Uri -import info.guardianproject.netcipher.client.TlsOnlySocketFactory import org.jsoup.Connection import org.jsoup.Jsoup import ru.yourok.torrserve.atv.Utils.isBrokenTCL @@ -45,7 +44,7 @@ object Net { .ignoreContentType(true) .method(Connection.Method.POST) if (!isBrokenTCL) - req.sslSocketFactory(insecureTlsSocketFactory()) + req.sslSocketFactory(TlsSocketFactory()) if (save) req.data("save", "true") req.data("title", title) @@ -69,7 +68,7 @@ object Net { .method(Connection.Method.POST) .maxBodySize(0) // The default maximum is 2MB, 0 = unlimited body if (!isBrokenTCL) - conn.sslSocketFactory(insecureTlsSocketFactory()) + conn.sslSocketFactory(TlsSocketFactory()) val auth = getAuthB64() if (auth.isNotEmpty()) @@ -98,7 +97,7 @@ object Net { .ignoreContentType(true) .timeout(duration) if (!isBrokenTCL) - conn.sslSocketFactory(insecureTlsSocketFactory()) + conn.sslSocketFactory(TlsSocketFactory()) val auth = getAuthB64() if (auth.isNotEmpty()) @@ -134,7 +133,7 @@ object Net { .ignoreContentType(true) .timeout(duration) if (!isBrokenTCL) - conn.sslSocketFactory(insecureTlsSocketFactory()) + conn.sslSocketFactory(TlsSocketFactory()) val response = conn.execute() @@ -153,40 +152,6 @@ object Net { } } - // https://stackoverflow.com/questions/26649389/how-to-disable-sslv3-in-android-for-httpsurlconnection - fun insecureTlsSocketFactory(): SSLSocketFactory { - val trustAllCerts = arrayOf(@SuppressLint("CustomX509TrustManager") - object : X509TrustManager { - @SuppressLint("TrustAllX509TrustManager") - @Throws(CertificateException::class) - override fun checkClientTrusted(chain: Array, authType: String) { - } - - @SuppressLint("TrustAllX509TrustManager") - @Throws(CertificateException::class) - override fun checkServerTrusted(chain: Array, authType: String) { - } - - override fun getAcceptedIssuers(): Array { - return arrayOf() - } - }) - - try { - val sslContext = SSLContext.getInstance("TLSv1.2") - sslContext.init(null, trustAllCerts, java.security.SecureRandom()) - return TlsOnlySocketFactory(sslContext.socketFactory) - } catch (e: Exception) { - when (e) { - is RuntimeException, is KeyManagementException -> { - throw RuntimeException("Failed to create a SSL socket factory", e) - } - - else -> throw e - } - } - } - fun isValidPublicIp4(ip: String?): Boolean { val address: Inet4Address? = try { InetAddress.getByName(ip) as? Inet4Address diff --git a/app/src/main/java/ru/yourok/torrserve/utils/TlsSocketFactory.kt b/app/src/main/java/ru/yourok/torrserve/utils/TlsSocketFactory.kt new file mode 100644 index 00000000..ccdfca55 --- /dev/null +++ b/app/src/main/java/ru/yourok/torrserve/utils/TlsSocketFactory.kt @@ -0,0 +1,98 @@ +package ru.yourok.torrserve.utils + +import org.conscrypt.Conscrypt +import java.io.IOException +import java.net.InetAddress +import java.net.Socket +import java.security.KeyManagementException +import java.security.NoSuchAlgorithmException +import java.security.Provider +import java.security.Security +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSocket +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.TrustManager + +class TlsSocketFactory : SSLSocketFactory { + private val enabledProtocols: Array + private val delegate: SSLSocketFactory + + constructor(enabledProtocols: Array) { + this.enabledProtocols = enabledProtocols + this.delegate = socketFactory + } + + constructor() { + this.enabledProtocols = TLS_RESTRICTED + this.delegate = socketFactory + } + + constructor(base: SSLSocketFactory) { + this.enabledProtocols = TLS_RESTRICTED + this.delegate = base + } + + override fun getDefaultCipherSuites(): Array { + return delegate.defaultCipherSuites + } + + override fun getSupportedCipherSuites(): Array { + return delegate.supportedCipherSuites + } + + @Throws(IOException::class) + override fun createSocket(): Socket { + return patch(delegate.createSocket()) + } + + @Throws(IOException::class) + override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket { + return patch(delegate.createSocket(s, host, port, autoClose)) + } + + @Throws(IOException::class) + override fun createSocket(host: String, port: Int): Socket { + return patch(delegate.createSocket(host, port)) + } + + @Throws(IOException::class) + override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket { + return patch(delegate.createSocket(host, port, localHost, localPort)) + } + + @Throws(IOException::class) + override fun createSocket(host: InetAddress, port: Int): Socket { + return patch(delegate.createSocket(host, port)) + } + + @Throws(IOException::class) + override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket { + return patch(delegate.createSocket(address, port, localAddress, localPort)) + } + + private fun patch(s: Socket): Socket { + if (s is SSLSocket) { + s.enabledProtocols = enabledProtocols + } + return s + } + + companion object { + private var conscrypt: Provider? = null + val TLS_MODERN: Array = arrayOf("TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3") + val TLS_RESTRICTED: Array = arrayOf("TLSv1.2", "TLSv1.3") + + @get:Throws(NoSuchAlgorithmException::class, KeyManagementException::class) + private val socketFactory: SSLSocketFactory + get() { + if (conscrypt == null) { + conscrypt = Conscrypt.newProvider() + // Add as provider + Security.insertProviderAt(conscrypt, 1) + } + val context = SSLContext.getInstance("TLS", conscrypt) + context.init(null, arrayOf(AllTrustManager()), null) + return context.socketFactory + } + } +} diff --git a/app/src/main/res/drawable-hdpi/ts_icon_white.png b/app/src/main/res/drawable-hdpi/ts_icon_white.png new file mode 100644 index 0000000000000000000000000000000000000000..b68c8d96df66f185b50173d275e13f7f533904f2 GIT binary patch literal 1293 zcmV+o1@iidP)^M$*?7OF+VYFZbNpT7AyL97Zwn=(0+@k_sfzeMa$?V0AVD@;mcU0gzFd}Q<-?ft2 z6RMAN&d#Cv08CCf-4}41mk>wBdBXTa1qXV?Hc19e;xtF_)MtIA2|hBhsdYa`>lvRb zn)GWWpPom+`DFH{E}D<7<=X<^Pyip9ECj$CDfmqiCEq@e6!SN1XkMnFmdTs|+DGto zba0)P!ot2Ocuz8Efc{_t-lJMa@KeEhfkau-^-7BMsKdH~WK=V8FbW<%M$DYexoEOC zXARe2#YHUo8^CbpNx;Vzzz<80(u_>FO~jH-c(M>%5=9C4Smz$c7}{dyJslZgb{%lm z+>plgy)~M+CM)ZrC?~Q&TR@$zQC$A@-9^o_3wJ^Lp)xt`t<@D!sjW<8SeHcHJ7buz zmKUuMC|%q^+GOB8F3DA41F>cx8DVkA$}-CY-U6by+cn_42z*MN3_N`YMxBOL#Z;oT z8!8Ij;~f`vJ2r@zJpp(+PfTM!i~ud&N2|!FG!{7Y9L6OY-mefrD4?Mpt5UH%$bU~@H%^S=ydnb(mmY+ zP@bqPP}a5p#A^UkOJ3ikHB^1!g%yuD4}!YjZ*(KH#>(^lvd#v0$17CxC+##YFM>bq z66Y?|av?;_zrREB^J`2_or%en+gUWB7MbMKT2lX9Qt+%|g*ty?lJqXJbEYLT^#uSv z#b@=Lpiz{-UEVd6Gf8WYFb*d(SU<2cYYE)8*Zh&c@Y8+OATIhXljSca+i}|CiIA;B(18v#_y;~F`#L=!q!X{nH3aWVwEQZE`#kvV+@=Dy{-Pqi8Qj2tX;{(GV4A_1 z?6=doKG0O7ClzUDZf;Swq2+3H+xEd{wA8pisB64mtaB}yWMLC{ww;Q=^OWG`jIb{9 zG{6U>sOYv9@n%H{zQB5gTZ;#3?qaXg26+3;U7hy&tam9o2zXS`;kDPfh2}Lh>ZF=a zg1*@WhuX2VFF4GunBtHm&#J_^1v$%Wh|aGe(Ya!hA0O>rG4UaG_R>!;=PZlSC0}8| zc3|J;H7m|lAx9zEwGb1xtz2}Q%K!bZ1}9=c{s}Mu#V6mbGT1+g00000NkvXXu0mjf DbHZnT literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ts_icon_white.png b/app/src/main/res/drawable-xhdpi/ts_icon_white.png new file mode 100644 index 0000000000000000000000000000000000000000..2007ce22303fe78f51dbfc360a090f7875a5dc10 GIT binary patch literal 1743 zcmV;=1~B=FP)q_A_+h=`EQ zfz??|3bC-bUC>gpY0g35V60pAAy+SMm6A?uacCCavHL(;F0Q)f7Fb-i%vg$O!Irvn)32pP+fPOw<<5sU`Z7 z>9_3+Y(Ccj#TuQD0j?*~9$Cnotvw`u?PHj(E&xn4f(3#P%WKqbGi5=f5kjC# zmOM?O6BD~1!NSe5sldb5?)W^B0A5^+SBUE%fYi`bLn4vl?{C05iESp>CBX9uw!cusv&@JO7ev1A?}`RW*(0Pa2&N_g5SV=S@O`@err2p^LG_cE(m6g2Q9QO}xylM*%ar4D)uN)nr()PcL2<{niO)R{&XGn7A^;jm9AdGtL!AIN ziMbCP?fyU!TKO7vo(v%$;@|D-z?bG}5ZH#wFvv3~i3X$~+0}~8&?2_?!S2@-&b810 zsrCW*rsdj3IBF7xWegn6y7Sqcn>!ltj95D%!mf^FJp1KJwd@2b)%JrRwdb(U4_f<0UIz@T zzRYNscRxZRl7GKYYlMO?BD|4HE?>#9NF+qt3kK)*KbctnjII?Sg)U@ zSw9z&kJ>QTL(&z)M5HPuMQ)yb^VSWe>R)>`^|pW5mK81F%s5{MAc&r80o0s@JHiul zrXd52SQ63x#ji8(jXY1NE7Z2@sQ(y|sW8=5!P0h?$}d)cOTBmFGp##?jsE9hlKuGA zi2Zx&d(T`yW{qySlx>=tL?(}e=ek)d5)Ej`8YES70knIS*Y~r}HpKteag4J2clT?} zUWz!nLO~N5fUlb*NM#c^F$o{z&j!rvgvIf9f~tOuc57vVmC9j7`smvn zHC0l5zVna47rvm4S~RvfbVXi$6Kpk$GIFP)>ZiEyP@%ploBX;C;McJRkZnTVTroWO z+%YYYWhTG@j0p*9Ll6ppJD^l7Wibs7iQK;_D|>OrH&L7F!r!kGt@&~ns=UsH_dH^f z0nxp=a3VBgE|7X}-%Hu^FZ0iN(zH(SQ+}V^2*nAJ^eB%ihN#wZqvEmit_(Z}0^p0C zuTyj5u6=1}`wbgRHAMr2YDNrf?gHSsUss{_$HM1b+1&Wqwg$W^><+0Y;_Pr#A%M7) zl$^79iO)%Jw`<1)N;Ho;Tq3qDKn&TJJV zOb}NQ0&j^SU?q4_;}q9yi>r2ZOYpI_Vl^x(VrF?uq?8Ggp-vIX#O!Tk8LehheVKQ) zxg^Ci!IJ7b>opdBV_}!1ghh?6$>CLYQT5x7`B?llISV1=H@W8l9v$W7U6eEu!Z>`S z_SwCU$1c2fs`#z`OhkO^+=D0r+tG{do lNmpN-yrL_5vgtno1^~K6$uO@bThjml002ovPDHLkV1oD;R4@Pl literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/torrent_files_item.xml b/app/src/main/res/layout/torrent_files_item.xml index b51f8258..17d831ed 100644 --- a/app/src/main/res/layout/torrent_files_item.xml +++ b/app/src/main/res/layout/torrent_files_item.xml @@ -24,28 +24,30 @@ android:id="@+id/ivViewed" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_alignParentTop="true" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" - android:layout_centerVertical="true" - android:layout_gravity="center|end" + android:layout_gravity="end|center" + android:layout_marginTop="-3dp" android:layout_marginEnd="3dp" android:layout_marginRight="3dp" app:srcCompat="@drawable/eye_show" /> + @@ -54,10 +56,10 @@ android:id="@+id/tvExt" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignTop="@+id/tvFileName" + android:layout_alignBottom="@+id/tvFileName" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" - android:layout_gravity="center" + android:layout_gravity="end|bottom" android:layout_margin="3dp" android:paddingStart="5dp" android:paddingLeft="5dp" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 73eeaa35..a085bf09 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -156,7 +156,7 @@ The local TorrServer is not running. Try clicking \"Exit\" in the Main Menu and launch the application again, or restart your device. If nothing changes after this, update TorrServer in the \"Update\" section. Select an available TorrServer from the list, or specify the address of a new one. Copied to clipboard - Service is running + TorrServer is running TorrServer is not responding Settings saved diff --git a/build.gradle b/build.gradle index f0e2ba0b..454b6f7f 100644 --- a/build.gradle +++ b/build.gradle @@ -6,10 +6,10 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.5.0' + classpath 'com.android.tools.build:gradle:8.6.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}" classpath 'com.google.gms:google-services:4.4.2' - classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.1' + classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.2' } }