From 66967d9e6db8ae75ae0f84bf9e89e7ce4718face Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Thu, 23 Apr 2020 13:51:42 +0200 Subject: [PATCH] Implement TLSv1.3 support --- .../ui/info/core/CoreInfoFragment.kt | 20 ++++++-- app/src/main/res/values-de/strings_info.xml | 1 + app/src/main/res/values-it/strings_info.xml | 1 + app/src/main/res/values-lt/strings_info.xml | 1 + app/src/main/res/values-pt/strings_info.xml | 1 + app/src/main/res/values/strings_info.xml | 1 + .../libquassel/util/nio/WrappedChannel.kt | 22 +++++++- .../libquassel/util/nio/WrappedChannelTest.kt | 50 +++++++++++++++++++ 8 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 lib/src/test/java/de/kuschku/libquassel/util/nio/WrappedChannelTest.kt diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/core/CoreInfoFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/info/core/CoreInfoFragment.kt index ce5dc9f95..699928862 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/info/core/CoreInfoFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/info/core/CoreInfoFragment.kt @@ -101,6 +101,7 @@ class CoreInfoFragment : ServiceBoundFragment() { private val movementMethod = BetterLinkMovementMethod.newInstance() private val cipherSuiteRegex = Regex("TLS_(.*)_WITH_(.*)") + private val cipherSuiteRegex13 = Regex("TLS_()(.*)") init { movementMethod.setOnLinkLongClickListener(LinkLongClickMenuHelper()) @@ -188,19 +189,30 @@ class CoreInfoFragment : ServiceBoundFragment() { secureDetails.visibility = View.GONE } + val protocol = it.orNull()?.protocol + val cipherSuiteRegex = + if (it.orNull()?.protocol == "TLSv1.3") cipherSuiteRegex13 + else cipherSuiteRegex + val (keyExchangeMechanism, cipherSuite) = it.orNull()?.cipherSuite?.let { cipherSuite -> cipherSuiteRegex.matchEntire(cipherSuite)?.destructured }?.let { (keyExchangeMechanism, cipherSuite) -> Pair(keyExchangeMechanism, cipherSuite) } ?: Pair(null, null) - val protocol = it.orNull()?.protocol + if (cipherSuite != null && keyExchangeMechanism != null && protocol != null) { + // TLSv1.3 has no key exchange mechanism in the ciphersuite + if (keyExchangeMechanism.isEmpty()) { + secureConnectionCiphersuite.text = context?.getString(R.string.label_core_connection_ciphersuite_13, + cipherSuite) + } else { + secureConnectionCiphersuite.text = context?.getString(R.string.label_core_connection_ciphersuite, + cipherSuite, + keyExchangeMechanism) + } secureConnectionProtocol.text = context?.getString(R.string.label_core_connection_protocol, protocol) - secureConnectionCiphersuite.text = context?.getString(R.string.label_core_connection_ciphersuite, - cipherSuite, - keyExchangeMechanism) secureConnectionProtocol.visibility = View.VISIBLE secureConnectionCiphersuite.visibility = View.VISIBLE } else { diff --git a/app/src/main/res/values-de/strings_info.xml b/app/src/main/res/values-de/strings_info.xml index 6483be3a0..090b79442 100644 --- a/app/src/main/res/values-de/strings_info.xml +++ b/app/src/main/res/values-de/strings_info.xml @@ -36,6 +36,7 @@ <string name="label_core_connection_verified_by">Verbindung verifiziert von %1$s</string> <string name="label_core_connection_protocol">Die Verbindung verwendet %1$s</string> <string name="label_core_connection_ciphersuite">Die Verbindung ist mit %1$s verschlüsselt und authentifiziert und verwendet %2$s als Mechanismus für den Schlüsselaustausch</string> + <string name="label_core_connection_ciphersuite_13">Die Verbindung ist mit %1$s verschlüsselt und authentifiziert</string> <string name="label_core_connection_verified_by_unknown">Unbekannt</string> <string name="label_core_connection_insecure">Unsichere Verbindung</string> diff --git a/app/src/main/res/values-it/strings_info.xml b/app/src/main/res/values-it/strings_info.xml index fb262774b..bf26b3df8 100644 --- a/app/src/main/res/values-it/strings_info.xml +++ b/app/src/main/res/values-it/strings_info.xml @@ -36,6 +36,7 @@ <string name="label_core_connection_verified_by">Connessione verificata da %1$s</string> <string name="label_core_connection_protocol">La connessione utilizza %1$s</string> <string name="label_core_connection_ciphersuite">La connessione è crittografata ed autenticata con %1$s ed utilizza %2$s come meccanismo di scambio delle chiavi</string> + <string name="label_core_connection_ciphersuite_13">La connessione è crittografata ed autenticata con %1$s</string> <string name="label_core_connection_verified_by_unknown">Sconosciuto</string> <string name="label_core_connection_insecure">Connessione insicura</string> diff --git a/app/src/main/res/values-lt/strings_info.xml b/app/src/main/res/values-lt/strings_info.xml index acc6d5301..54ae9fa0d 100644 --- a/app/src/main/res/values-lt/strings_info.xml +++ b/app/src/main/res/values-lt/strings_info.xml @@ -36,6 +36,7 @@ <string name="label_core_connection_verified_by">Ryšys patvirtintas %1$s</string> <string name="label_core_connection_protocol">Šis ryšys naudoja %1$s</string> <string name="label_core_connection_ciphersuite">Šis ryšys yra užkoduotas ir autentifikuotas naudojant %1$s, ir naudoja %2$s raktų apsikeitimui</string> + <string name="label_core_connection_ciphersuite_13">Šis ryšys yra užkoduotas ir autentifikuotas naudojant %1$s</string> <string name="label_core_connection_verified_by_unknown">Nežinoma</string> <string name="label_core_connection_insecure">Nesaugus ryšys</string> diff --git a/app/src/main/res/values-pt/strings_info.xml b/app/src/main/res/values-pt/strings_info.xml index 5308e8887..1e377ba15 100644 --- a/app/src/main/res/values-pt/strings_info.xml +++ b/app/src/main/res/values-pt/strings_info.xml @@ -36,6 +36,7 @@ <string name="label_core_connection_verified_by">Ligação verificada por %1$s</string> <string name="label_core_connection_protocol">A ligação utiliza %1$s</string> <string name="label_core_connection_ciphersuite">A ligação é encriptada e autenticada utilizando %1$s e usa %2$s como mecanismo de troca de chaves</string> + <string name="label_core_connection_ciphersuite_13">A ligação é encriptada e autenticada utilizando %1$s</string> <string name="label_core_connection_verified_by_unknown">Desconhecido</string> <string name="label_core_connection_insecure">Ligação insegura</string> diff --git a/app/src/main/res/values/strings_info.xml b/app/src/main/res/values/strings_info.xml index 089e3f32d..827ec1560 100644 --- a/app/src/main/res/values/strings_info.xml +++ b/app/src/main/res/values/strings_info.xml @@ -36,6 +36,7 @@ <string name="label_core_connection_verified_by">Connection verified by %1$s</string> <string name="label_core_connection_protocol">The connection uses %1$s</string> <string name="label_core_connection_ciphersuite">The connection is encrypted and authenticated using %1$s and uses %2$s as the key exchange mechanism</string> + <string name="label_core_connection_ciphersuite_13">The connection is encrypted and authenticated using %1$s</string> <string name="label_core_connection_verified_by_unknown">Unknown</string> <string name="label_core_connection_insecure">Insecure Connection</string> diff --git a/lib/src/main/java/de/kuschku/libquassel/util/nio/WrappedChannel.kt b/lib/src/main/java/de/kuschku/libquassel/util/nio/WrappedChannel.kt index ce101ab93..878ea7fc8 100644 --- a/lib/src/main/java/de/kuschku/libquassel/util/nio/WrappedChannel.kt +++ b/lib/src/main/java/de/kuschku/libquassel/util/nio/WrappedChannel.kt @@ -22,6 +22,8 @@ package de.kuschku.libquassel.util.nio import de.kuschku.libquassel.connection.HostnameVerifier import de.kuschku.libquassel.connection.SocketAddress import de.kuschku.libquassel.util.compatibility.CompatibilityUtils +import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log +import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel import de.kuschku.libquassel.util.compatibility.StreamChannelFactory import java.io.* import java.net.Socket @@ -32,6 +34,7 @@ import java.nio.channels.InterruptibleChannel import java.nio.channels.ReadableByteChannel import java.nio.channels.WritableByteChannel import java.security.GeneralSecurityException +import java.security.NoSuchAlgorithmException import java.security.cert.X509Certificate import java.util.zip.InflaterInputStream import javax.net.ssl.SSLContext @@ -60,6 +63,8 @@ class WrappedChannel private constructor( } companion object { + const val DEFAULT_TLS_VERSION = "TLSv1.2" + fun ofSocket(s: Socket, closeListeners: List<Closeable> = emptyList()): WrappedChannel { return WrappedChannel( s, @@ -68,6 +73,12 @@ class WrappedChannel private constructor( closeListeners = closeListeners + s.getInputStream() + s.getOutputStream() ) } + + fun selectBestTlsVersion(availableVersions: Array<String>): String? { + return availableVersions.filter { + it.startsWith("TLSv") && it >= "TLSv1.2" + }.sorted().lastOrNull() + } } fun withCompression(): WrappedChannel { @@ -82,7 +93,16 @@ class WrappedChannel private constructor( @Throws(GeneralSecurityException::class, IOException::class) fun withSSL(certificateManager: X509TrustManager, hostnameVerifier: HostnameVerifier, address: SocketAddress): WrappedChannel { - val context = SSLContext.getInstance("TLSv1.2") + val tlsVersion = try { + selectBestTlsVersion(SSLContext.getDefault().defaultSSLParameters.protocols) + } catch (e: NoSuchAlgorithmException) { + null + } + log(LogLevel.DEBUG, + "WrappedChannel", + "TLS Version chosen is: $tlsVersion, with fallback $DEFAULT_TLS_VERSION") + + val context = SSLContext.getInstance(tlsVersion ?: DEFAULT_TLS_VERSION) val managers = arrayOf(certificateManager) context.init(null, managers, null) val factory = context.socketFactory diff --git a/lib/src/test/java/de/kuschku/libquassel/util/nio/WrappedChannelTest.kt b/lib/src/test/java/de/kuschku/libquassel/util/nio/WrappedChannelTest.kt new file mode 100644 index 000000000..ff5bbc085 --- /dev/null +++ b/lib/src/test/java/de/kuschku/libquassel/util/nio/WrappedChannelTest.kt @@ -0,0 +1,50 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2020 Janne Mareike Koschinski + * Copyright (c) 2020 The Quassel Project + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.libquassel.util.nio + +import de.kuschku.libquassel.util.nio.WrappedChannel.Companion.selectBestTlsVersion +import org.junit.Assert.assertEquals +import org.junit.Test + +class WrappedChannelTest { + @Test + fun doesNotSelectOutdatedTlsVersions() { + assertEquals(null, selectBestTlsVersion(arrayOf( + "SSLv3", "TLSv1", "TLSv1.0", "TLSv1.1" + ))) + } + + @Test + fun rejectsNonTlsProtocols() { + assertEquals(null, selectBestTlsVersion(arrayOf( + "SSLv3", "UberSecurityProtocol5" + ))) + } + + @Test + fun selectsLatestTlsVersion() { + assertEquals("TLSv1.2", selectBestTlsVersion(arrayOf( + "SSLv3", "TLSv1", "TLSv1.0", "TLSv1.1", "TLSv1.2", "UberSecurityProtocol5" + ))) + assertEquals("TLSv1.3", selectBestTlsVersion(arrayOf( + "SSLv3", "TLSv1", "TLSv1.0", "TLSv1.1", "TLSv1.2", "TLSv1.3", "UberSecurityProtocol5" + ))) + } +} -- GitLab