[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.net.URL
import java.security.KeyStore import java.security.KeyStore
import java.security.cert.Certificate import java.security.cert.Certificate
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
@@ -47,26 +48,67 @@ object ApiClient {
} }
} }
fun createOkHttpClientWithDynamicCert(trustedCert: X509Certificate): OkHttpClient { fun createOkHttpClientWithDynamicCert(trustedCert: X509Certificate?): OkHttpClient {
try { try {
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 { val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null, null) load(null, null)
setCertificateEntry("ca", trustedCert) setCertificateEntry("ca", it)
} }
val tmf = TrustManagerFactory.getInstance(
val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm() TrustManagerFactory.getDefaultAlgorithm()
val tmf = TrustManagerFactory.getInstance(tmfAlgorithm).apply { ).apply {
init(keyStore) init(keyStore)
} }
tmf.trustManagers.first { it is X509TrustManager } as X509TrustManager
}
val trustManager = tmf.trustManagers.first { it is X509TrustManager } as X509TrustManager val combinedTm = object : X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate> {
return (defaultTm.acceptedIssuers + (customTm?.acceptedIssuers ?: emptyArray()))
}
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 { val sslContext = SSLContext.getInstance("TLS").apply {
init(null, arrayOf(trustManager), null) init(null, arrayOf(combinedTm), null)
} }
return OkHttpClient.Builder() return OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, trustManager) .sslSocketFactory(sslContext.socketFactory, combinedTm)
.build() .build()
} catch (e: Exception) { } catch (e: Exception) {
@@ -74,9 +116,9 @@ object ApiClient {
} }
} }
fun createOkHttp(): OkHttpClient fun createOkHttp(cert: String?): OkHttpClient {
{ val trustedCert = cert?.let { loadCertificateFromString(it) }
return createOkHttpClientWithDynamicCert(loadCertificateFromString(cert)) return createOkHttpClientWithDynamicCert(trustedCert)
} }
private fun createRetrofit(): Retrofit { 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.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
@@ -70,7 +71,7 @@ fun VariableGrid(
Layout( Layout(
modifier = modifier modifier = modifier
.verticalScroll(scrollState), // ✅ 支持垂直滚动 .verticalScroll(scrollState),
content = content content = content
) { measurables, constraints -> ) { measurables, constraints ->
@@ -137,6 +138,7 @@ fun ComicScreen(
) { ) {
comicScreenViewModel.SetupClient() comicScreenViewModel.SetupClient()
val included = comicScreenViewModel.included val included = comicScreenViewModel.included
val state = rememberLazyGridState()
Column { Column {
@@ -185,7 +187,8 @@ fun ComicScreen(
columns = GridCells.Adaptive(128.dp), columns = GridCells.Adaptive(128.dp),
contentPadding = PaddingValues(8.dp), contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp),
state = state
) )
{ {
items(comicScreenViewModel.comics.filter { x -> items(comicScreenViewModel.comics.filter { x ->