mirror of
https://github.com/bitfireAT/davx5-ose.git
synced 2025-12-23 23:17:50 -05:00
Only use cert4android when needed (#1802)
* Refactor ClientCertKeyManager and HttpClientBuilder - Add logging to `ClientCertKeyManager` for better error handling. - Update `HttpClientBuilder` to conditionally use custom trust manager and hostname verifier based on `allowCustomCerts` flag. - Rename `customCertsUI` to `allowCustomCerts` in build configuration. * Update trust manager and hostname verifier selection logic - Improve logging and error handling in `ClientCertKeyManager` * App settings: hide certificate settings when custom certificates are not allowed * Typo
This commit is contained in:
@@ -27,7 +27,8 @@ android {
|
|||||||
minSdk = 24 // Android 7.0
|
minSdk = 24 // Android 7.0
|
||||||
targetSdk = 36 // Android 16
|
targetSdk = 36 // Android 16
|
||||||
|
|
||||||
buildConfigField("boolean", "customCertsUI", "true")
|
// whether the build supports and allows to use custom certificates
|
||||||
|
buildConfigField("boolean", "allowCustomCerts", "true")
|
||||||
|
|
||||||
testInstrumentationRunner = "at.bitfire.davdroid.HiltTestRunner"
|
testInstrumentationRunner = "at.bitfire.davdroid.HiltTestRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,22 +6,31 @@ package at.bitfire.davdroid.network
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.security.KeyChain
|
import android.security.KeyChain
|
||||||
|
import android.security.KeyChainException
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import java.net.Socket
|
import java.net.Socket
|
||||||
import java.security.Principal
|
import java.security.Principal
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import java.util.logging.Level
|
||||||
|
import java.util.logging.Logger
|
||||||
import javax.net.ssl.X509ExtendedKeyManager
|
import javax.net.ssl.X509ExtendedKeyManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KeyManager that provides a client certificate and private key from the Android KeyChain.
|
* KeyManager that provides a client certificate and private key from the Android [KeyChain].
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if the alias doesn't exist or is not accessible
|
* Requests for certificates / private keys for other aliases than the specified one
|
||||||
|
* will be ignored.
|
||||||
|
*
|
||||||
|
* @param alias alias of the desired certificate / private key
|
||||||
*/
|
*/
|
||||||
class ClientCertKeyManager @AssistedInject constructor(
|
class ClientCertKeyManager @AssistedInject constructor(
|
||||||
@Assisted private val alias: String,
|
@Assisted private val alias: String,
|
||||||
@ApplicationContext private val context: Context
|
@ApplicationContext private val context: Context,
|
||||||
|
private val logger: Logger
|
||||||
): X509ExtendedKeyManager() {
|
): X509ExtendedKeyManager() {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
@@ -29,19 +38,42 @@ class ClientCertKeyManager @AssistedInject constructor(
|
|||||||
fun create(alias: String): ClientCertKeyManager
|
fun create(alias: String): ClientCertKeyManager
|
||||||
}
|
}
|
||||||
|
|
||||||
val certs = KeyChain.getCertificateChain(context, alias) ?: throw IllegalArgumentException("Alias doesn't exist or not accessible: $alias")
|
|
||||||
val key = KeyChain.getPrivateKey(context, alias) ?: throw IllegalArgumentException("Alias doesn't exist or not accessible: $alias")
|
|
||||||
|
|
||||||
override fun getServerAliases(p0: String?, p1: Array<out Principal>?): Array<String>? = null
|
override fun getServerAliases(p0: String?, p1: Array<out Principal>?): Array<String>? = null
|
||||||
override fun chooseServerAlias(p0: String?, p1: Array<out Principal>?, p2: Socket?) = null
|
override fun chooseServerAlias(p0: String?, p1: Array<out Principal>?, p2: Socket?) = null
|
||||||
|
|
||||||
override fun getClientAliases(p0: String?, p1: Array<out Principal>?) = arrayOf(alias)
|
override fun getClientAliases(p0: String?, p1: Array<out Principal>?) = arrayOf(alias)
|
||||||
override fun chooseClientAlias(p0: Array<out String>?, p1: Array<out Principal>?, p2: Socket?) = alias
|
override fun chooseClientAlias(p0: Array<out String>?, p1: Array<out Principal>?, p2: Socket?) = alias
|
||||||
|
|
||||||
override fun getCertificateChain(forAlias: String?) =
|
override fun getCertificateChain(forAlias: String): Array<X509Certificate>? {
|
||||||
certs.takeIf { forAlias == alias }
|
if (forAlias != alias)
|
||||||
|
return null
|
||||||
|
|
||||||
override fun getPrivateKey(forAlias: String?) =
|
return try {
|
||||||
key.takeIf { forAlias == alias }
|
KeyChain.getCertificateChain(context, alias).also { result ->
|
||||||
|
if (result == null)
|
||||||
|
logger.warning("Couldn't obtain certificate chain for alias $alias")
|
||||||
|
}
|
||||||
|
} catch (e: KeyChainException) {
|
||||||
|
// Android <Q throws an exception instead of returning null
|
||||||
|
logger.log(Level.WARNING, "Couldn't obtain certificate chain for alias $alias", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPrivateKey(forAlias: String): PrivateKey? {
|
||||||
|
if (forAlias != alias)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return try {
|
||||||
|
KeyChain.getPrivateKey(context, alias).also { result ->
|
||||||
|
if (result == null)
|
||||||
|
logger.log(Level.WARNING, "Couldn't obtain private key for alias $alias")
|
||||||
|
}
|
||||||
|
} catch (e: KeyChainException) {
|
||||||
|
// Android <Q throws an exception instead of returning null
|
||||||
|
logger.log(Level.WARNING, "Couldn't obtain private key for alias $alias", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -33,12 +33,16 @@ import okhttp3.internal.tls.OkHostnameVerifier
|
|||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
|
import java.security.KeyStore
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.net.ssl.HostnameVerifier
|
||||||
import javax.net.ssl.KeyManager
|
import javax.net.ssl.KeyManager
|
||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.SSLContext
|
||||||
|
import javax.net.ssl.TrustManagerFactory
|
||||||
|
import javax.net.ssl.X509TrustManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder for the [OkHttpClient].
|
* Builder for the [OkHttpClient].
|
||||||
@@ -79,6 +83,7 @@ class HttpClientBuilder @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var loggerInterceptorLevel: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.BODY
|
private var loggerInterceptorLevel: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.BODY
|
||||||
|
|
||||||
fun loggerInterceptorLevel(level: HttpLoggingInterceptor.Level): HttpClientBuilder {
|
fun loggerInterceptorLevel(level: HttpLoggingInterceptor.Level): HttpClientBuilder {
|
||||||
loggerInterceptorLevel = level
|
loggerInterceptorLevel = level
|
||||||
return this
|
return this
|
||||||
@@ -86,6 +91,7 @@ class HttpClientBuilder @Inject constructor(
|
|||||||
|
|
||||||
// default cookie store for non-persistent cookies (some services like Horde use cookies for session tracking)
|
// default cookie store for non-persistent cookies (some services like Horde use cookies for session tracking)
|
||||||
private var cookieStore: CookieJar = MemoryCookieStore()
|
private var cookieStore: CookieJar = MemoryCookieStore()
|
||||||
|
|
||||||
fun setCookieStore(cookieStore: CookieJar): HttpClientBuilder {
|
fun setCookieStore(cookieStore: CookieJar): HttpClientBuilder {
|
||||||
this.cookieStore = cookieStore
|
this.cookieStore = cookieStore
|
||||||
return this
|
return this
|
||||||
@@ -94,6 +100,7 @@ class HttpClientBuilder @Inject constructor(
|
|||||||
private var authenticationInterceptor: Interceptor? = null
|
private var authenticationInterceptor: Interceptor? = null
|
||||||
private var authenticator: Authenticator? = null
|
private var authenticator: Authenticator? = null
|
||||||
private var certificateAlias: String? = null
|
private var certificateAlias: String? = null
|
||||||
|
|
||||||
fun authenticate(host: String?, getCredentials: () -> Credentials, updateAuthState: ((AuthState) -> Unit)? = null): HttpClientBuilder {
|
fun authenticate(host: String?, getCredentials: () -> Credentials, updateAuthState: ((AuthState) -> Unit)? = null): HttpClientBuilder {
|
||||||
val credentials = getCredentials()
|
val credentials = getCredentials()
|
||||||
if (credentials.authState != null) {
|
if (credentials.authState != null) {
|
||||||
@@ -130,6 +137,7 @@ class HttpClientBuilder @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var followRedirects = false
|
private var followRedirects = false
|
||||||
|
|
||||||
fun followRedirects(follow: Boolean): HttpClientBuilder {
|
fun followRedirects(follow: Boolean): HttpClientBuilder {
|
||||||
followRedirects = follow
|
followRedirects = follow
|
||||||
return this
|
return this
|
||||||
@@ -224,7 +232,8 @@ class HttpClientBuilder @Inject constructor(
|
|||||||
// app-wide custom proxy support
|
// app-wide custom proxy support
|
||||||
buildProxy(okBuilder)
|
buildProxy(okBuilder)
|
||||||
|
|
||||||
// add authentication
|
// add connection security (including client certificates) and authentication
|
||||||
|
buildConnectionSecurity(okBuilder)
|
||||||
buildAuthentication(okBuilder)
|
buildAuthentication(okBuilder)
|
||||||
|
|
||||||
// add network logging, if requested
|
// add network logging, if requested
|
||||||
@@ -246,15 +255,17 @@ class HttpClientBuilder @Inject constructor(
|
|||||||
// basic/digest auth and OAuth
|
// basic/digest auth and OAuth
|
||||||
authenticationInterceptor?.let { okBuilder.addInterceptor(it) }
|
authenticationInterceptor?.let { okBuilder.addInterceptor(it) }
|
||||||
authenticator?.let { okBuilder.authenticator(it) }
|
authenticator?.let { okBuilder.authenticator(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildConnectionSecurity(okBuilder: OkHttpClient.Builder) {
|
||||||
// client certificate
|
// client certificate
|
||||||
val keyManager: KeyManager? = certificateAlias?.let { alias ->
|
val clientKeyManager: KeyManager? = certificateAlias?.let { alias ->
|
||||||
try {
|
try {
|
||||||
val manager = keyManagerFactory.create(alias)
|
val manager = keyManagerFactory.create(alias)
|
||||||
logger.fine("Using certificate $alias for authentication")
|
logger.fine("Using certificate $alias for authentication")
|
||||||
|
|
||||||
// HTTP/2 doesn't support client certificates (yet)
|
// HTTP/2 doesn't support client certificates (yet)
|
||||||
// see https://tools.ietf.org/html/draft-ietf-httpbis-http2-secondary-certs-04
|
// see https://datatracker.ietf.org/doc/draft-ietf-httpbis-secondary-server-certs/
|
||||||
okBuilder.protocols(listOf(Protocol.HTTP_1_1))
|
okBuilder.protocols(listOf(Protocol.HTTP_1_1))
|
||||||
|
|
||||||
manager
|
manager
|
||||||
@@ -264,25 +275,49 @@ class HttpClientBuilder @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cert4android integration
|
// select trust manager and hostname verifier depending on whether custom certificates are allowed
|
||||||
val certManager = CustomCertManager(
|
val customTrustManager: X509TrustManager?
|
||||||
context = context,
|
val customHostnameVerifier: HostnameVerifier?
|
||||||
trustSystemCerts = !settingsManager.getBoolean(Settings.DISTRUST_SYSTEM_CERTIFICATES),
|
|
||||||
appInForeground = if (BuildConfig.customCertsUI)
|
|
||||||
ForegroundTracker.inForeground // interactive mode
|
|
||||||
else
|
|
||||||
null // non-interactive mode
|
|
||||||
)
|
|
||||||
|
|
||||||
val sslContext = SSLContext.getInstance("TLS")
|
if (BuildConfig.allowCustomCerts) {
|
||||||
sslContext.init(
|
// use cert4android for custom certificate handling
|
||||||
/* km = */ if (keyManager != null) arrayOf(keyManager) else null,
|
customTrustManager = CustomCertManager(
|
||||||
/* tm = */ arrayOf(certManager),
|
context = context,
|
||||||
/* random = */ null
|
trustSystemCerts = !settingsManager.getBoolean(Settings.DISTRUST_SYSTEM_CERTIFICATES),
|
||||||
)
|
appInForeground = ForegroundTracker.inForeground
|
||||||
okBuilder
|
)
|
||||||
.sslSocketFactory(sslContext.socketFactory, certManager)
|
// allow users to accept certificates with wrong host names
|
||||||
.hostnameVerifier(certManager.HostnameVerifier(OkHostnameVerifier))
|
customHostnameVerifier = customTrustManager.HostnameVerifier(OkHostnameVerifier)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// no custom certificates, use default trust manager and hostname verifier
|
||||||
|
customTrustManager = null
|
||||||
|
customHostnameVerifier = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// change settings only if we have at least only one custom component
|
||||||
|
if (clientKeyManager != null || customTrustManager != null) {
|
||||||
|
val trustManager = customTrustManager ?: defaultTrustManager()
|
||||||
|
|
||||||
|
// use trust manager and client key manager (if defined) for TLS connections
|
||||||
|
val sslContext = SSLContext.getInstance("TLS")
|
||||||
|
sslContext.init(
|
||||||
|
/* km = */ if (clientKeyManager != null) arrayOf(clientKeyManager) else null,
|
||||||
|
/* tm = */ arrayOf(trustManager),
|
||||||
|
/* random = */ null
|
||||||
|
)
|
||||||
|
okBuilder.sslSocketFactory(sslContext.socketFactory, trustManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
// also add the custom hostname verifier (if defined)
|
||||||
|
if (customHostnameVerifier != null)
|
||||||
|
okBuilder.hostnameVerifier(customHostnameVerifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun defaultTrustManager(): X509TrustManager {
|
||||||
|
val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||||
|
factory.init(null as KeyStore?)
|
||||||
|
return factory.trustManagers.filterIsInstance<X509TrustManager>().first()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildProxy(okBuilder: OkHttpClient.Builder) {
|
private fun buildProxy(okBuilder: OkHttpClient.Builder) {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import at.bitfire.davdroid.BuildConfig
|
||||||
import at.bitfire.davdroid.R
|
import at.bitfire.davdroid.R
|
||||||
import at.bitfire.davdroid.settings.Settings
|
import at.bitfire.davdroid.settings.Settings
|
||||||
import at.bitfire.davdroid.ui.AppSettingsModel.PushDistributorInfo
|
import at.bitfire.davdroid.ui.AppSettingsModel.PushDistributorInfo
|
||||||
@@ -107,10 +108,11 @@ fun AppSettingsScreen(
|
|||||||
onProxyPortUpdated = model::updateProxyPort,
|
onProxyPortUpdated = model::updateProxyPort,
|
||||||
|
|
||||||
// Security
|
// Security
|
||||||
|
onNavPermissionsScreen = onNavPermissionsScreen,
|
||||||
|
showCertSettings = BuildConfig.allowCustomCerts,
|
||||||
distrustSystemCerts = model.distrustSystemCertificates().collectAsStateWithLifecycle(null).value ?: false,
|
distrustSystemCerts = model.distrustSystemCertificates().collectAsStateWithLifecycle(null).value ?: false,
|
||||||
onDistrustSystemCertsUpdated = model::updateDistrustSystemCertificates,
|
onDistrustSystemCertsUpdated = model::updateDistrustSystemCertificates,
|
||||||
onResetCertificates = model::resetCertificates,
|
onResetCertificates = model::resetCertificates,
|
||||||
onNavPermissionsScreen = onNavPermissionsScreen,
|
|
||||||
|
|
||||||
// User interface
|
// User interface
|
||||||
onShowNotificationSettings = onShowNotificationSettings,
|
onShowNotificationSettings = onShowNotificationSettings,
|
||||||
@@ -149,10 +151,11 @@ fun AppSettingsScreen(
|
|||||||
onProxyPortUpdated: (Int) -> Unit,
|
onProxyPortUpdated: (Int) -> Unit,
|
||||||
|
|
||||||
// AppSettings security
|
// AppSettings security
|
||||||
|
onNavPermissionsScreen: () -> Unit,
|
||||||
|
showCertSettings: Boolean,
|
||||||
distrustSystemCerts: Boolean,
|
distrustSystemCerts: Boolean,
|
||||||
onDistrustSystemCertsUpdated: (Boolean) -> Unit,
|
onDistrustSystemCertsUpdated: (Boolean) -> Unit,
|
||||||
onResetCertificates: () -> Unit,
|
onResetCertificates: () -> Unit,
|
||||||
onNavPermissionsScreen: () -> Unit,
|
|
||||||
|
|
||||||
// AppSettings UserInterface
|
// AppSettings UserInterface
|
||||||
theme: Int,
|
theme: Int,
|
||||||
@@ -224,6 +227,8 @@ fun AppSettingsScreen(
|
|||||||
|
|
||||||
val resetCertificatesSuccessMessage = stringResource(R.string.app_settings_reset_certificates_success)
|
val resetCertificatesSuccessMessage = stringResource(R.string.app_settings_reset_certificates_success)
|
||||||
AppSettings_Security(
|
AppSettings_Security(
|
||||||
|
onNavPermissionsScreen = onNavPermissionsScreen,
|
||||||
|
showCertSettings = showCertSettings,
|
||||||
distrustSystemCerts = distrustSystemCerts,
|
distrustSystemCerts = distrustSystemCerts,
|
||||||
onDistrustSystemCertsUpdated = onDistrustSystemCertsUpdated,
|
onDistrustSystemCertsUpdated = onDistrustSystemCertsUpdated,
|
||||||
onResetCertificates = {
|
onResetCertificates = {
|
||||||
@@ -231,8 +236,7 @@ fun AppSettingsScreen(
|
|||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
snackbarHostState.showSnackbar(resetCertificatesSuccessMessage)
|
snackbarHostState.showSnackbar(resetCertificatesSuccessMessage)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
onNavPermissionsScreen = onNavPermissionsScreen
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val resetHintsSuccessMessage = stringResource(R.string.app_settings_reset_hints_success)
|
val resetHintsSuccessMessage = stringResource(R.string.app_settings_reset_hints_success)
|
||||||
@@ -282,9 +286,10 @@ fun AppSettingsScreen_Preview() {
|
|||||||
onNavUp = {},
|
onNavUp = {},
|
||||||
onProxyTypeUpdated = {},
|
onProxyTypeUpdated = {},
|
||||||
onProxyPortUpdated = {},
|
onProxyPortUpdated = {},
|
||||||
|
onNavPermissionsScreen = {},
|
||||||
|
showCertSettings = true,
|
||||||
onDistrustSystemCertsUpdated = {},
|
onDistrustSystemCertsUpdated = {},
|
||||||
onResetCertificates = {},
|
onResetCertificates = {},
|
||||||
onNavPermissionsScreen = {},
|
|
||||||
onThemeSelected = {},
|
onThemeSelected = {},
|
||||||
onResetHints = {},
|
onResetHints = {},
|
||||||
tasksAppName = "No tasks app",
|
tasksAppName = "No tasks app",
|
||||||
@@ -420,48 +425,51 @@ fun AppSettings_Connection(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppSettings_Security(
|
fun AppSettings_Security(
|
||||||
|
onNavPermissionsScreen: () -> Unit,
|
||||||
|
showCertSettings: Boolean,
|
||||||
distrustSystemCerts: Boolean,
|
distrustSystemCerts: Boolean,
|
||||||
onDistrustSystemCertsUpdated: (Boolean) -> Unit,
|
onDistrustSystemCertsUpdated: (Boolean) -> Unit,
|
||||||
onResetCertificates: () -> Unit,
|
onResetCertificates: () -> Unit
|
||||||
onNavPermissionsScreen: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
SettingsHeader(divider = true) {
|
SettingsHeader(divider = true) {
|
||||||
Text(stringResource(R.string.app_settings_security))
|
Text(stringResource(R.string.app_settings_security))
|
||||||
}
|
}
|
||||||
|
|
||||||
var showingDistrustWarning by remember { mutableStateOf(false) }
|
|
||||||
if (showingDistrustWarning) {
|
|
||||||
DistrustSystemCertificatesAlertDialog(
|
|
||||||
onDistrustSystemCertsRequested = { onDistrustSystemCertsUpdated(true) },
|
|
||||||
onDismissRequested = { showingDistrustWarning = false }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SwitchSetting(
|
|
||||||
checked = distrustSystemCerts,
|
|
||||||
name = stringResource(R.string.app_settings_distrust_system_certs),
|
|
||||||
summaryOn = stringResource(R.string.app_settings_distrust_system_certs_on),
|
|
||||||
summaryOff = stringResource(R.string.app_settings_distrust_system_certs_off)
|
|
||||||
) { checked ->
|
|
||||||
if (checked) {
|
|
||||||
// Show warning before enabling.
|
|
||||||
showingDistrustWarning = true
|
|
||||||
} else {
|
|
||||||
onDistrustSystemCertsUpdated(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Setting(
|
|
||||||
name = stringResource(R.string.app_settings_reset_certificates),
|
|
||||||
summary = stringResource(R.string.app_settings_reset_certificates_summary),
|
|
||||||
onClick = onResetCertificates
|
|
||||||
)
|
|
||||||
|
|
||||||
Setting(
|
Setting(
|
||||||
name = stringResource(R.string.app_settings_security_app_permissions),
|
name = stringResource(R.string.app_settings_security_app_permissions),
|
||||||
summary = stringResource(R.string.app_settings_security_app_permissions_summary),
|
summary = stringResource(R.string.app_settings_security_app_permissions_summary),
|
||||||
onClick = onNavPermissionsScreen
|
onClick = onNavPermissionsScreen
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (showCertSettings) {
|
||||||
|
var showingDistrustWarning by remember { mutableStateOf(false) }
|
||||||
|
if (showingDistrustWarning) {
|
||||||
|
DistrustSystemCertificatesAlertDialog(
|
||||||
|
onDistrustSystemCertsRequested = { onDistrustSystemCertsUpdated(true) },
|
||||||
|
onDismissRequested = { showingDistrustWarning = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchSetting(
|
||||||
|
checked = distrustSystemCerts,
|
||||||
|
name = stringResource(R.string.app_settings_distrust_system_certs),
|
||||||
|
summaryOn = stringResource(R.string.app_settings_distrust_system_certs_on),
|
||||||
|
summaryOff = stringResource(R.string.app_settings_distrust_system_certs_off)
|
||||||
|
) { checked ->
|
||||||
|
if (checked) {
|
||||||
|
// Show warning before enabling.
|
||||||
|
showingDistrustWarning = true
|
||||||
|
} else {
|
||||||
|
onDistrustSystemCertsUpdated(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Setting(
|
||||||
|
name = stringResource(R.string.app_settings_reset_certificates),
|
||||||
|
summary = stringResource(R.string.app_settings_reset_certificates_summary),
|
||||||
|
onClick = onResetCertificates
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
Reference in New Issue
Block a user