In readUrlWithCustomCas, use both custom and system CA certificates

This commit is contained in:
Juha Heinanen
2025-10-17 09:14:52 +03:00
parent 3b7f45844a
commit 9495a7d399
+59 -17
View File
@@ -1,5 +1,6 @@
package com.tutpro.baresip package com.tutpro.baresip
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.KeyguardManager import android.app.KeyguardManager
import android.content.ContentResolver import android.content.ContentResolver
@@ -48,7 +49,9 @@ import java.net.SocketException
import java.net.URL import java.net.URL
import java.security.KeyStore import java.security.KeyStore
import java.security.SecureRandom import java.security.SecureRandom
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.text.DateFormat import java.text.DateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Enumeration import java.util.Enumeration
@@ -67,6 +70,7 @@ import javax.crypto.spec.SecretKeySpec
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
import kotlin.text.replaceFirstChar import kotlin.text.replaceFirstChar
object Utils { object Utils {
@@ -1008,36 +1012,74 @@ object Utils {
return null return null
} }
// 1. Create a CertificateFactory and load the user's certificate try {
// 1. Create a TrustManager that trusts the CAs in the user-provided file
val customTrustManager = fun(): X509TrustManager {
val certificateFactory = CertificateFactory.getInstance("X.509") val certificateFactory = CertificateFactory.getInstance("X.509")
val certificateInputStream = caFile.inputStream() val certificateInputStream = caFile.inputStream()
val userCertificate = certificateFactory.generateCertificate(certificateInputStream) // generateCertificates (plural) is crucial for loading all certs from the file
val certificates = certificateFactory.generateCertificates(certificateInputStream)
certificateInputStream.close() certificateInputStream.close()
// 2. Create a KeyStore containing our trusted CAs val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType)
keyStore.load(null, null) keyStore.load(null, null)
keyStore.setCertificateEntry("user_ca", userCertificate) certificates.forEachIndexed { index, certificate ->
keyStore.setCertificateEntry("user_ca_$index", certificate)
}
// 3. Create a TrustManager that trusts the CAs in our KeyStore val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
val tmf = TrustManagerFactory.getInstance(tmfAlgorithm)
tmf.init(keyStore) tmf.init(keyStore)
return tmf.trustManagers.find { it is X509TrustManager } as X509TrustManager
}()
// 4. Create an SSLContext that uses our TrustManager // 2. Create a TrustManager that trusts the default system CAs
val systemTrustManager = fun(): X509TrustManager {
val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
factory.init(null as KeyStore?) // A null keystore loads the system's default CAs
return factory.trustManagers.find { it is X509TrustManager } as X509TrustManager
}()
// 3. Create a composite TrustManager that delegates to both system and custom CAs
@SuppressLint("CustomX509TrustManager")
val compositeTrustManager = object : X509TrustManager {
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
// This is for client certificate authentication, which you are not using.
// It's safe to just delegate to the system manager by default.
systemTrustManager.checkClientTrusted(chain, authType)
}
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
try {
// First, try to validate the chain with the system's default TrustManager.
systemTrustManager.checkServerTrusted(chain, authType)
} catch (_: CertificateException) {
// If that fails, and only if that fails, try to validate with our custom TrustManager.
// This will throw the final CertificateException if it also fails, which is the correct behavior.
customTrustManager.checkServerTrusted(chain, authType)
}
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
// Return a combined list of issuers from both trust managers.
return systemTrustManager.acceptedIssuers + customTrustManager.acceptedIssuers
}
}
// 4. Create an SSLContext that uses our new composite TrustManager
val sslContext = SSLContext.getInstance("TLS") val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, tmf.trustManagers, null) sslContext.init(null, arrayOf(compositeTrustManager), null)
// 5. Tell HttpsURLConnection to use our custom SSLContext // 5. Tell HttpsURLConnection to use our custom SSLContext for this connection
val urlConnection = url.openConnection() as HttpsURLConnection val urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.sslSocketFactory = sslContext.socketFactory urlConnection.sslSocketFactory = sslContext.socketFactory
// 6. Proceed with the connection // 6. Proceed with the connection and return the result
return try { return urlConnection.inputStream.bufferedReader().use { it.readText() }
urlConnection.inputStream.bufferedReader().use { it.readText() }
} finally { } catch (e: Exception) {
urlConnection.disconnect() // Catch any exception from certificate loading or from the network connection and log it
Log.e("Utils", "readUrlWithCustomCa failed: ${e.message}")
return null
} }
} }