[feat] Dynamic certificate authentication

This commit is contained in:
acite
2025-09-10 14:51:07 +08:00
parent f6583ffcf1
commit b48f8ce6b0
2 changed files with 61 additions and 16 deletions

View File

@@ -19,6 +19,7 @@ import java.net.InetAddress
import java.net.URL
import java.security.KeyStore
import java.security.cert.Certificate
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
@@ -47,26 +48,67 @@ object ApiClient {
}
}
fun createOkHttpClientWithDynamicCert(trustedCert: X509Certificate): OkHttpClient {
fun createOkHttpClientWithDynamicCert(trustedCert: X509Certificate?): OkHttpClient {
try {
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null, null)
setCertificateEntry("ca", trustedCert)
val defaultTmFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
).apply {
init(null as KeyStore?)
}
val defaultTm = defaultTmFactory.trustManagers
.first { it is X509TrustManager } as X509TrustManager
val customTm: X509TrustManager? = trustedCert?.let {
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null, null)
setCertificateEntry("ca", it)
}
val tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
).apply {
init(keyStore)
}
tmf.trustManagers.first { it is X509TrustManager } as X509TrustManager
}
val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
val tmf = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
init(keyStore)
}
val combinedTm = object : X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate> {
return (defaultTm.acceptedIssuers + (customTm?.acceptedIssuers ?: emptyArray()))
}
val trustManager = tmf.trustManagers.first { it is X509TrustManager } as X509TrustManager
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
var passed = false
try {
defaultTm.checkClientTrusted(chain, authType)
passed = true
} catch (_: CertificateException) { }
if (!passed && customTm != null) {
customTm.checkClientTrusted(chain, authType)
passed = true
}
if (!passed) throw CertificateException("Untrusted client certificate chain")
}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
var passed = false
try {
defaultTm.checkServerTrusted(chain, authType)
passed = true
} catch (_: CertificateException) { }
if (!passed && customTm != null) {
customTm.checkServerTrusted(chain, authType)
passed = true
}
if (!passed) throw CertificateException("Untrusted server certificate chain")
}
}
val sslContext = SSLContext.getInstance("TLS").apply {
init(null, arrayOf(trustManager), null)
init(null, arrayOf(combinedTm), null)
}
return OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, trustManager)
.sslSocketFactory(sslContext.socketFactory, combinedTm)
.build()
} catch (e: Exception) {
@@ -74,9 +116,9 @@ object ApiClient {
}
}
fun createOkHttp(): OkHttpClient
{
return createOkHttpClientWithDynamicCert(loadCertificateFromString(cert))
fun createOkHttp(cert: String?): OkHttpClient {
val trustedCert = cert?.let { loadCertificateFromString(it) }
return createOkHttpClientWithDynamicCert(trustedCert)
}
private fun createRetrofit(): Retrofit {

View File

@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
@@ -70,7 +71,7 @@ fun VariableGrid(
Layout(
modifier = modifier
.verticalScroll(scrollState), // ✅ 支持垂直滚动
.verticalScroll(scrollState),
content = content
) { measurables, constraints ->
@@ -137,6 +138,7 @@ fun ComicScreen(
) {
comicScreenViewModel.SetupClient()
val included = comicScreenViewModel.included
val state = rememberLazyGridState()
Column {
@@ -185,7 +187,8 @@ fun ComicScreen(
columns = GridCells.Adaptive(128.dp),
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
horizontalArrangement = Arrangement.spacedBy(8.dp),
state = state
)
{
items(comicScreenViewModel.comics.filter { x ->