From bc66a44f0ae29dc679d1a11b2ea2255885a00bed Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Wed, 19 Nov 2025 12:15:07 +0100 Subject: [PATCH 01/12] Move TestCertStore to separate file --- .../cert4android/CustomCertManagerTest.kt | 80 ---------------- .../at/bitfire/cert4android/TestCertStore.kt | 96 +++++++++++++++++++ 2 files changed, 96 insertions(+), 80 deletions(-) create mode 100644 lib/src/test/java/at/bitfire/cert4android/TestCertStore.kt diff --git a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt index 274aa0c..4631ab2 100644 --- a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt +++ b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt @@ -128,84 +128,4 @@ class CustomCertManagerTest { } } - - class TestCertStore(): CertStore { - - private val logger - get() = Logger.getLogger(javaClass.name) - - /** custom TrustStore (simple map) */ - @VisibleForTesting - internal val userKeyStore = mutableMapOf() - - /** in-memory store for untrusted certs */ - @VisibleForTesting - internal var untrustedCerts = HashSet() - - @Synchronized - override fun clearUserDecisions() { - logger.info("Clearing user-(dis)trusted certificates") - - for (alias in userKeyStore.keys) - userKeyStore.remove(alias) - - // clear untrusted certs - untrustedCerts.clear() - } - - /** - * Determines whether a certificate chain is trusted. - */ - override fun isTrusted(chain: Array, authType: String, trustSystemCerts: Boolean, appInForeground: StateFlow?): Boolean { - if (chain.isEmpty()) - throw IllegalArgumentException("Certificate chain must not be empty") - val cert = chain[0] - - synchronized(this) { - // explicitly accepted by user? - if (isTrustedByUser(cert)) - return true - - // explicitly rejected by user? - if (untrustedCerts.contains(cert)) - return false - - // trusted by system? (if applicable) - if (trustSystemCerts) - return true // system trusts all certificates - } - logger.log(Level.INFO, "Certificate not known and running in non-interactive mode, rejecting") - return false - } - - /** - * Determines whether a certificate has been explicitly accepted by the user. In this case, - * we can ignore an invalid host name for that certificate. - */ - @Synchronized - override fun isTrustedByUser(cert: X509Certificate): Boolean = - userKeyStore.containsValue(cert) - - @Synchronized - override fun setTrustedByUser(cert: X509Certificate) { - val alias = CertUtils.getTag(cert) - logger.info("Trusted by user: ${cert.subjectDN.name} ($alias)") - userKeyStore[alias] = cert - untrustedCerts -= cert - } - - @Synchronized - override fun setUntrustedByUser(cert: X509Certificate) { - logger.info("Distrusted by user: ${cert.subjectDN.name}") - - // find certificate - val alias = userKeyStore.entries.find { it.value == cert }?.key - if (alias != null) - // and delete, if applicable - userKeyStore.remove(alias) - untrustedCerts += cert - } - - } - } \ No newline at end of file diff --git a/lib/src/test/java/at/bitfire/cert4android/TestCertStore.kt b/lib/src/test/java/at/bitfire/cert4android/TestCertStore.kt new file mode 100644 index 0000000..8dcde1b --- /dev/null +++ b/lib/src/test/java/at/bitfire/cert4android/TestCertStore.kt @@ -0,0 +1,96 @@ +/* + * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package at.bitfire.cert4android + +import androidx.annotation.VisibleForTesting +import kotlinx.coroutines.flow.StateFlow +import java.security.cert.X509Certificate +import java.util.logging.Level +import java.util.logging.Logger + +class TestCertStore(): CertStore { + + private val logger + get() = Logger.getLogger(javaClass.name) + + /** custom TrustStore (simple map) */ + @VisibleForTesting + internal val userKeyStore = mutableMapOf() + + /** in-memory store for untrusted certs */ + @VisibleForTesting + internal var untrustedCerts = HashSet() + + @Synchronized + override fun clearUserDecisions() { + logger.info("Clearing user-(dis)trusted certificates") + + for (alias in userKeyStore.keys) + userKeyStore.remove(alias) + + // clear untrusted certs + untrustedCerts.clear() + } + + /** + * Determines whether a certificate chain is trusted. + */ + override fun isTrusted(chain: Array, authType: String, trustSystemCerts: Boolean, appInForeground: StateFlow?): Boolean { + if (chain.isEmpty()) + throw IllegalArgumentException("Certificate chain must not be empty") + val cert = chain[0] + + synchronized(this) { + // explicitly accepted by user? + if (isTrustedByUser(cert)) + return true + + // explicitly rejected by user? + if (untrustedCerts.contains(cert)) + return false + + // trusted by system? (if applicable) + if (trustSystemCerts) + return true // system trusts all certificates + } + logger.log(Level.INFO, "Certificate not known and running in non-interactive mode, rejecting") + return false + } + + /** + * Determines whether a certificate has been explicitly accepted by the user. In this case, + * we can ignore an invalid host name for that certificate. + */ + @Synchronized + override fun isTrustedByUser(cert: X509Certificate): Boolean = + userKeyStore.containsValue(cert) + + @Synchronized + override fun setTrustedByUser(cert: X509Certificate) { + val alias = CertUtils.getTag(cert) + logger.info("Trusted by user: ${cert.subjectDN.name} ($alias)") + userKeyStore[alias] = cert + untrustedCerts -= cert + } + + @Synchronized + override fun setUntrustedByUser(cert: X509Certificate) { + logger.info("Distrusted by user: ${cert.subjectDN.name}") + + // find certificate + val alias = userKeyStore.entries.find { it.value == cert }?.key + if (alias != null) + // and delete, if applicable + userKeyStore.remove(alias) + untrustedCerts += cert + } + +} \ No newline at end of file From 082e511120fdcf5ab4b22832baad342b7782fa50 Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Wed, 19 Nov 2025 12:37:26 +0100 Subject: [PATCH 02/12] Add connect and read timeouts --- .../cert4android/CustomCertManagerTest.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt index 4631ab2..5628728 100644 --- a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt +++ b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt @@ -10,18 +10,16 @@ package at.bitfire.cert4android -import androidx.annotation.VisibleForTesting -import kotlinx.coroutines.flow.StateFlow import org.junit.Assume.assumeNotNull import org.junit.Before import org.junit.Test import java.io.IOException +import java.net.InetSocketAddress +import java.net.Socket import java.net.URL import java.security.SecureRandom import java.security.cert.CertificateException import java.security.cert.X509Certificate -import java.util.logging.Level -import java.util.logging.Logger import javax.net.ssl.SSLContext import javax.net.ssl.SSLSocket import javax.net.ssl.TrustManager @@ -117,9 +115,14 @@ class CustomCertManagerTest { } // Create an SSL socket and force a TLS handshake - // (HttpsURLConnection performs the handshake lazily and sometimes the handshake is not - // executed before this method gets called) - sslContext.socketFactory.createSocket(host, port).use { socket -> + val socket = Socket().apply { + soTimeout = 5000 // read timeout + connect( + InetSocketAddress(host, port), + 5000 // connect timeout + ) + } + sslContext.socketFactory.createSocket(socket, host, port, true).use { socket -> val sslSocket = socket as SSLSocket // Explicitly start the handshake (gets certificate) sslSocket.startHandshake() From d0380dad06fc22ac8fe4dfe4caf3684bb87df44c Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Wed, 19 Nov 2025 14:22:22 +0100 Subject: [PATCH 03/12] Use HttpsURLConnection again --- .../cert4android/CustomCertManagerTest.kt | 62 ++++++++----------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt index 5628728..8007343 100644 --- a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt +++ b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt @@ -10,19 +10,16 @@ package at.bitfire.cert4android +import android.net.SSLCertificateSocketFactory +import org.apache.http.conn.ssl.AllowAllHostnameVerifier import org.junit.Assume.assumeNotNull import org.junit.Before import org.junit.Test import java.io.IOException -import java.net.InetSocketAddress -import java.net.Socket import java.net.URL -import java.security.SecureRandom import java.security.cert.CertificateException import java.security.cert.X509Certificate -import javax.net.ssl.SSLContext -import javax.net.ssl.SSLSocket -import javax.net.ssl.TrustManager +import javax.net.ssl.HttpsURLConnection import javax.net.ssl.X509TrustManager class CustomCertManagerTest { @@ -99,35 +96,30 @@ class CustomCertManagerTest { * @return the certificates of the site */ fun getSiteCertificates(url: URL): List { - val port = if (url.port != -1) url.port else 443 - val host = url.host - - // Create a TrustManager which accepts all certificates - val trustAll = object : X509TrustManager { - override fun checkClientTrusted(chain: Array?, authType: String?) {} - override fun checkServerTrusted(chain: Array?, authType: String?) {} - override fun getAcceptedIssuers(): Array = emptyArray() - } - - // Create an SSLContext using the trust-all manager - val sslContext = SSLContext.getInstance("TLS").apply { - init(null, arrayOf(trustAll), SecureRandom()) - } - - // Create an SSL socket and force a TLS handshake - val socket = Socket().apply { - soTimeout = 5000 // read timeout - connect( - InetSocketAddress(host, port), - 5000 // connect timeout - ) - } - sslContext.socketFactory.createSocket(socket, host, port, true).use { socket -> - val sslSocket = socket as SSLSocket - // Explicitly start the handshake (gets certificate) - sslSocket.startHandshake() - // server certificates now available in SSLSession - return sslSocket.session.peerCertificates.map { it as X509Certificate } + val conn = url.openConnection() as HttpsURLConnection + try { + conn.hostnameVerifier = AllowAllHostnameVerifier() + conn.sslSocketFactory = object : SSLCertificateSocketFactory(1000) { + init { + setTrustManagers(arrayOf(object : X509TrustManager { + override fun checkClientTrusted( + chain: Array?, + authType: String? + ) { /* OK */ } + override fun checkServerTrusted( + chain: Array?, + authType: String? + ) { /* OK */ } + override fun getAcceptedIssuers(): Array? = emptyArray() + })) + } + } + conn.inputStream.read() + val certs = mutableListOf() + conn.serverCertificates.forEach { certs += it as X509Certificate } + return certs + } finally { + conn.disconnect() } } From 13044658d3708d9b34f3a87193f41740203ce8ea Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Wed, 19 Nov 2025 15:01:13 +0100 Subject: [PATCH 04/12] Use SSLContext instead of SSLCertificateSocketFactory --- .../cert4android/CustomCertManagerTest.kt | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt index 8007343..4285812 100644 --- a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt +++ b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt @@ -10,16 +10,18 @@ package at.bitfire.cert4android -import android.net.SSLCertificateSocketFactory import org.apache.http.conn.ssl.AllowAllHostnameVerifier import org.junit.Assume.assumeNotNull import org.junit.Before import org.junit.Test import java.io.IOException import java.net.URL +import java.security.SecureRandom import java.security.cert.CertificateException import java.security.cert.X509Certificate import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager import javax.net.ssl.X509TrustManager class CustomCertManagerTest { @@ -99,21 +101,17 @@ class CustomCertManagerTest { val conn = url.openConnection() as HttpsURLConnection try { conn.hostnameVerifier = AllowAllHostnameVerifier() - conn.sslSocketFactory = object : SSLCertificateSocketFactory(1000) { - init { - setTrustManagers(arrayOf(object : X509TrustManager { - override fun checkClientTrusted( - chain: Array?, - authType: String? - ) { /* OK */ } - override fun checkServerTrusted( - chain: Array?, - authType: String? - ) { /* OK */ } - override fun getAcceptedIssuers(): Array? = emptyArray() - })) - } - } + conn.sslSocketFactory = SSLContext.getInstance("TLS").apply { + init( + null, + arrayOf(object : X509TrustManager { + override fun checkClientTrusted(chain: Array?, authType: String?) {} + override fun checkServerTrusted(chain: Array?, authType: String?) {} + override fun getAcceptedIssuers(): Array = emptyArray() + }), + SecureRandom() + ) + }.socketFactory conn.inputStream.read() val certs = mutableListOf() conn.serverCertificates.forEach { certs += it as X509Certificate } From 3d73f870d8654a501480258ec7ec946e41aaf578 Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Wed, 19 Nov 2025 15:01:50 +0100 Subject: [PATCH 05/12] Replace AllowAllHostnameVerifier with HostnameVerifier --- .../java/at/bitfire/cert4android/CustomCertManagerTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt index 4285812..d501065 100644 --- a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt +++ b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt @@ -10,7 +10,6 @@ package at.bitfire.cert4android -import org.apache.http.conn.ssl.AllowAllHostnameVerifier import org.junit.Assume.assumeNotNull import org.junit.Before import org.junit.Test @@ -19,6 +18,7 @@ import java.net.URL import java.security.SecureRandom import java.security.cert.CertificateException import java.security.cert.X509Certificate +import javax.net.ssl.HostnameVerifier import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext import javax.net.ssl.TrustManager @@ -100,7 +100,7 @@ class CustomCertManagerTest { fun getSiteCertificates(url: URL): List { val conn = url.openConnection() as HttpsURLConnection try { - conn.hostnameVerifier = AllowAllHostnameVerifier() + conn.hostnameVerifier = HostnameVerifier { _, _ -> true } conn.sslSocketFactory = SSLContext.getInstance("TLS").apply { init( null, From 203925dd6166da803a6b71ed37e0f474ccd404a6 Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Wed, 19 Nov 2025 15:15:47 +0100 Subject: [PATCH 06/12] Add timeouts --- .../test/java/at/bitfire/cert4android/CustomCertManagerTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt index d501065..3a6d0a5 100644 --- a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt +++ b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt @@ -100,6 +100,8 @@ class CustomCertManagerTest { fun getSiteCertificates(url: URL): List { val conn = url.openConnection() as HttpsURLConnection try { + conn.connectTimeout = 5000 + conn.readTimeout = 5000 conn.hostnameVerifier = HostnameVerifier { _, _ -> true } conn.sslSocketFactory = SSLContext.getInstance("TLS").apply { init( From e8d4f9aa5f1823a21fe8db7210c6517c63e469ed Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Wed, 19 Nov 2025 15:26:44 +0100 Subject: [PATCH 07/12] Remove obsolete parenthesis --- lib/src/test/java/at/bitfire/cert4android/TestCertStore.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/test/java/at/bitfire/cert4android/TestCertStore.kt b/lib/src/test/java/at/bitfire/cert4android/TestCertStore.kt index 8dcde1b..3b4bef0 100644 --- a/lib/src/test/java/at/bitfire/cert4android/TestCertStore.kt +++ b/lib/src/test/java/at/bitfire/cert4android/TestCertStore.kt @@ -16,7 +16,7 @@ import java.security.cert.X509Certificate import java.util.logging.Level import java.util.logging.Logger -class TestCertStore(): CertStore { +class TestCertStore: CertStore { private val logger get() = Logger.getLogger(javaClass.name) From 9f59985d266fef08d867b9fd6e7cfaa82cbfd81b Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Wed, 19 Nov 2025 15:27:46 +0100 Subject: [PATCH 08/12] Use use to close stream automatically --- .../at/bitfire/cert4android/CustomCertManagerTest.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt index 3a6d0a5..99fe9da 100644 --- a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt +++ b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt @@ -114,10 +114,12 @@ class CustomCertManagerTest { SecureRandom() ) }.socketFactory - conn.inputStream.read() - val certs = mutableListOf() - conn.serverCertificates.forEach { certs += it as X509Certificate } - return certs + conn.inputStream.use { + it.read() + val certs = mutableListOf() + conn.serverCertificates.forEach { certs += it as X509Certificate } + return certs + } } finally { conn.disconnect() } From e7a62c48e202a7aa95b6f76ddeae13006b97c2c2 Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Thu, 20 Nov 2025 10:19:42 +0100 Subject: [PATCH 09/12] Move certificate retrieval to @BeforeClass --- .../cert4android/CustomCertManagerTest.kt | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt index 99fe9da..1475bd9 100644 --- a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt +++ b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt @@ -10,8 +10,9 @@ package at.bitfire.cert4android -import org.junit.Assume.assumeNotNull +import org.junit.AssumptionViolatedException import org.junit.Before +import org.junit.BeforeClass import org.junit.Test import java.io.IOException import java.net.URL @@ -30,16 +31,6 @@ class CustomCertManagerTest { private lateinit var certManager: CustomCertManager private lateinit var paranoidCertManager: CustomCertManager - private var siteCerts: List? = - try { - getSiteCertificates(URL("https://www.davx5.com")) - } catch(_: IOException) { - null - } - init { - assumeNotNull("Couldn't load certificate from Web", siteCerts) - } - @Before fun createCertManager() { certStore = TestCertStore() @@ -55,18 +46,18 @@ class CustomCertManagerTest { @Test fun testTrustedCertificate() { - certManager.checkServerTrusted(siteCerts!!.toTypedArray(), "RSA") + certManager.checkServerTrusted(siteCerts.toTypedArray(), "RSA") } @Test(expected = CertificateException::class) fun testParanoidCertificate() { - paranoidCertManager.checkServerTrusted(siteCerts!!.toTypedArray(), "RSA") + paranoidCertManager.checkServerTrusted(siteCerts.toTypedArray(), "RSA") } @Test fun testAddCustomCertificate() { addTrustedCertificate() - paranoidCertManager.checkServerTrusted(siteCerts!!.toTypedArray(), "RSA") + paranoidCertManager.checkServerTrusted(siteCerts.toTypedArray(), "RSA") } @Test(expected = CertificateException::class) @@ -77,51 +68,60 @@ class CustomCertManagerTest { // should now be rejected for the whole session addUntrustedCertificate() - paranoidCertManager.checkServerTrusted(siteCerts!!.toTypedArray(), "RSA") + paranoidCertManager.checkServerTrusted(siteCerts.toTypedArray(), "RSA") } // helpers private fun addTrustedCertificate() { - certStore.setTrustedByUser(siteCerts!!.first()) + certStore.setTrustedByUser(siteCerts.first()) } private fun addUntrustedCertificate() { - certStore.setUntrustedByUser(siteCerts!!.first()) + certStore.setUntrustedByUser(siteCerts.first()) } - /** - * Get the certificates of a site (bypassing all trusted checks). - * - * @param url the URL to get the certificates from - * @return the certificates of the site - */ - fun getSiteCertificates(url: URL): List { - val conn = url.openConnection() as HttpsURLConnection - try { - conn.connectTimeout = 5000 - conn.readTimeout = 5000 - conn.hostnameVerifier = HostnameVerifier { _, _ -> true } - conn.sslSocketFactory = SSLContext.getInstance("TLS").apply { - init( - null, - arrayOf(object : X509TrustManager { - override fun checkClientTrusted(chain: Array?, authType: String?) {} - override fun checkServerTrusted(chain: Array?, authType: String?) {} - override fun getAcceptedIssuers(): Array = emptyArray() - }), - SecureRandom() - ) - }.socketFactory - conn.inputStream.use { - it.read() - val certs = mutableListOf() - conn.serverCertificates.forEach { certs += it as X509Certificate } - return certs + companion object { + private lateinit var siteCerts: List + + @JvmStatic + @BeforeClass + fun setUp() { + siteCerts = try { + getSiteCertificates(URL("https://www.davx5.com")) + } catch (_: IOException) { + // Skip all tests if the certs can't be fetched + throw AssumptionViolatedException("Couldn't load certificate from Web") + } + } + + fun getSiteCertificates(url: URL): List { + val conn = url.openConnection() as HttpsURLConnection + try { + conn.connectTimeout = 5000 + conn.readTimeout = 5000 + conn.hostnameVerifier = HostnameVerifier { _, _ -> true } + conn.sslSocketFactory = SSLContext.getInstance("TLS").apply { + init( + null, + arrayOf(object : X509TrustManager { + override fun checkClientTrusted(chain: Array?, authType: String?) {} + override fun checkServerTrusted(chain: Array?, authType: String?) {} + override fun getAcceptedIssuers(): Array = emptyArray() + }), + SecureRandom() + ) + }.socketFactory + conn.inputStream.use { stream -> + stream.read() + val certs = mutableListOf() + conn.serverCertificates.forEach { certs += it as X509Certificate } + return certs + } + } finally { + conn.disconnect() } - } finally { - conn.disconnect() } } From 36d66d0e9fad1ff4ec795041fc44908938ba0117 Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Thu, 20 Nov 2025 11:35:19 +0100 Subject: [PATCH 10/12] Accept only valid hostnames and certificates --- .../cert4android/CustomCertManagerTest.kt | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt index 1475bd9..6fa4846 100644 --- a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt +++ b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt @@ -16,14 +16,9 @@ import org.junit.BeforeClass import org.junit.Test import java.io.IOException import java.net.URL -import java.security.SecureRandom import java.security.cert.CertificateException import java.security.cert.X509Certificate -import javax.net.ssl.HostnameVerifier import javax.net.ssl.HttpsURLConnection -import javax.net.ssl.SSLContext -import javax.net.ssl.TrustManager -import javax.net.ssl.X509TrustManager class CustomCertManagerTest { @@ -101,18 +96,6 @@ class CustomCertManagerTest { try { conn.connectTimeout = 5000 conn.readTimeout = 5000 - conn.hostnameVerifier = HostnameVerifier { _, _ -> true } - conn.sslSocketFactory = SSLContext.getInstance("TLS").apply { - init( - null, - arrayOf(object : X509TrustManager { - override fun checkClientTrusted(chain: Array?, authType: String?) {} - override fun checkServerTrusted(chain: Array?, authType: String?) {} - override fun getAcceptedIssuers(): Array = emptyArray() - }), - SecureRandom() - ) - }.socketFactory conn.inputStream.use { stream -> stream.read() val certs = mutableListOf() From 3e897caec020b6955c670335d63eb0766a259434 Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Thu, 20 Nov 2025 11:38:09 +0100 Subject: [PATCH 11/12] Replace @BeforeClass with by-lazy --- .../cert4android/CustomCertManagerTest.kt | 45 +++++++------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt index 6fa4846..35c24d3 100644 --- a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt +++ b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt @@ -10,11 +10,8 @@ package at.bitfire.cert4android -import org.junit.AssumptionViolatedException import org.junit.Before -import org.junit.BeforeClass import org.junit.Test -import java.io.IOException import java.net.URL import java.security.cert.CertificateException import java.security.cert.X509Certificate @@ -22,6 +19,9 @@ import javax.net.ssl.HttpsURLConnection class CustomCertManagerTest { + private val siteCerts: List by lazy { + getSiteCertificates(URL("https://www.davx5.com")) + } private lateinit var certStore: CertStore private lateinit var certManager: CustomCertManager private lateinit var paranoidCertManager: CustomCertManager @@ -77,34 +77,19 @@ class CustomCertManagerTest { certStore.setUntrustedByUser(siteCerts.first()) } - companion object { - private lateinit var siteCerts: List - - @JvmStatic - @BeforeClass - fun setUp() { - siteCerts = try { - getSiteCertificates(URL("https://www.davx5.com")) - } catch (_: IOException) { - // Skip all tests if the certs can't be fetched - throw AssumptionViolatedException("Couldn't load certificate from Web") - } - } - - fun getSiteCertificates(url: URL): List { - val conn = url.openConnection() as HttpsURLConnection - try { - conn.connectTimeout = 5000 - conn.readTimeout = 5000 - conn.inputStream.use { stream -> - stream.read() - val certs = mutableListOf() - conn.serverCertificates.forEach { certs += it as X509Certificate } - return certs - } - } finally { - conn.disconnect() + fun getSiteCertificates(url: URL): List { + val conn = url.openConnection() as HttpsURLConnection + try { + conn.connectTimeout = 5000 + conn.readTimeout = 5000 + conn.inputStream.use { stream -> + stream.read() + val certs = mutableListOf() + conn.serverCertificates.forEach { certs += it as X509Certificate } + return certs } + } finally { + conn.disconnect() } } From 8533a5d4dd82ba7df735e9eeb078181858d7c0ea Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Thu, 20 Nov 2025 11:39:54 +0100 Subject: [PATCH 12/12] Simplify certificate retrieval --- .../java/at/bitfire/cert4android/CustomCertManagerTest.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt index 35c24d3..09c0524 100644 --- a/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt +++ b/lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt @@ -82,11 +82,8 @@ class CustomCertManagerTest { try { conn.connectTimeout = 5000 conn.readTimeout = 5000 - conn.inputStream.use { stream -> - stream.read() - val certs = mutableListOf() - conn.serverCertificates.forEach { certs += it as X509Certificate } - return certs + conn.inputStream.use { + return conn.serverCertificates.filterIsInstance() } } finally { conn.disconnect()