Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ class OkhttpTest {
// set cert4android TrustManager and HostnameVerifier
val certManager = CustomCertManager(
CustomCertStore.getInstance(context),
trustSystemCerts = true,
appInForeground = null
object : SettingsProvider {
override val appInForeground = null
override val trustSystemCerts = true
}
)

val sslContext = SSLContext.getInstance("TLS")
Expand Down
3 changes: 1 addition & 2 deletions lib/src/main/java/at/bitfire/cert4android/CertStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

package at.bitfire.cert4android

import kotlinx.coroutines.flow.StateFlow
import java.security.cert.X509Certificate

interface CertStore {
Expand All @@ -23,7 +22,7 @@ interface CertStore {
/**
* Determines whether a certificate chain is trusted.
*/
fun isTrusted(chain: Array<X509Certificate>, authType: String, trustSystemCerts: Boolean, appInForeground: StateFlow<Boolean>?): Boolean
fun isTrusted(chain: Array<X509Certificate>, authType: String, trustSystemCerts: Boolean, appInForeground: Boolean?): Boolean

/**
* Determines whether a certificate has been explicitly accepted by the user. In this case,
Expand Down
26 changes: 17 additions & 9 deletions lib/src/main/java/at/bitfire/cert4android/CustomCertManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
package at.bitfire.cert4android

import android.annotation.SuppressLint
import kotlinx.coroutines.flow.StateFlow
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import java.util.logging.Logger
Expand All @@ -21,16 +20,13 @@ import javax.net.ssl.X509TrustManager
/**
* TrustManager to handle custom certificates.
*
* @param trustSystemCerts whether system certificates will be trusted
* @param appInForeground - `true`: if needed, directly launches [TrustCertificateActivity] and shows notification (if possible)
* - `false`: if needed, shows notification (if possible)
* - `null`: non-interactive mode: does not show notification or launch activity
* @param certStore certificate store with (un)trusted certificates
* @param settings settings provider to get settings from
*/
@SuppressLint("CustomX509TrustManager")
class CustomCertManager @JvmOverloads constructor(
private val certStore: CertStore,
val trustSystemCerts: Boolean = true,
var appInForeground: StateFlow<Boolean>?
private val settings: SettingsProvider
): X509TrustManager {

private val logger
Expand All @@ -51,7 +47,13 @@ class CustomCertManager @JvmOverloads constructor(
*/
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
if (!certStore.isTrusted(chain, authType, trustSystemCerts, appInForeground))
if (!certStore.isTrusted(
chain,
authType,
trustSystemCerts = settings.trustSystemCerts,
appInForeground = settings.appInForeground
)
)
throw CertificateException("Certificate chain not trusted")
}

Expand All @@ -75,7 +77,13 @@ class CustomCertManager @JvmOverloads constructor(
// Allow users to explicitly accept certificates that have a bad hostname here
(session.peerCertificates.firstOrNull() as? X509Certificate)?.let { cert ->
// Check without trusting system certificates so that the user will be asked even for system-trusted certificates
if (certStore.isTrusted(arrayOf(cert), "RSA", false, appInForeground))
if (certStore.isTrusted(
arrayOf(cert),
"RSA",
trustSystemCerts = false,
appInForeground = settings.appInForeground
)
)
return true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import android.annotation.SuppressLint
import android.content.Context
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import java.io.File
Expand Down Expand Up @@ -75,7 +74,7 @@ class CustomCertStore internal constructor(
/**
* Determines whether a certificate chain is trusted.
*/
override fun isTrusted(chain: Array<X509Certificate>, authType: String, trustSystemCerts: Boolean, appInForeground: StateFlow<Boolean>?): Boolean {
override fun isTrusted(chain: Array<X509Certificate>, authType: String, trustSystemCerts: Boolean, appInForeground: Boolean?): Boolean {
if (chain.isEmpty())
throw IllegalArgumentException("Certificate chain must not be empty")
val cert = chain[0]
Expand Down Expand Up @@ -111,7 +110,7 @@ class CustomCertStore internal constructor(

try {
withTimeout(userTimeout) {
ui.check(cert, appInForeground.value)
ui.check(cert, appInForeground)
}
} catch (_: TimeoutCancellationException) {
logger.log(Level.WARNING, "User timeout while waiting for certificate decision, rejecting")
Expand Down
37 changes: 37 additions & 0 deletions lib/src/main/java/at/bitfire/cert4android/SettingsProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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

/**
* Provides settings for cert4android. Implementations can override the getters.
*
* Usually implemented by the app which uses cert4android, and then passed to cert4android classes
* which need it.
*/
interface SettingsProvider {

/**
* The app foreground status:
*
* - `true`: foreground – directly launch UI ([TrustCertificateActivity]) and show notification (if possible)
* - `false`: background – only show notification (if possible)
* - `null`: non-interactive mode – don't show notification or launch activity
*/
val appInForeground: Boolean?

/**
* Whether system certificates shall be trusted.
*
* @return `true` if system certificates are considered trustworthy, `false` otherwise
*/
val trustSystemCerts: Boolean

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,14 @@ class CustomCertManagerTest {
@Before
fun createCertManager() {
certStore = TestCertStore()
certManager = CustomCertManager(certStore, true, null)
paranoidCertManager = CustomCertManager(certStore, false, null)
certManager = CustomCertManager(certStore, object : SettingsProvider {
override val appInForeground = null
override val trustSystemCerts = true
})
paranoidCertManager = CustomCertManager(certStore, object : SettingsProvider {
override val appInForeground = null
override val trustSystemCerts = false
})
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
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
Expand Down Expand Up @@ -43,7 +42,7 @@ class TestCertStore: CertStore {
/**
* Determines whether a certificate chain is trusted.
*/
override fun isTrusted(chain: Array<X509Certificate>, authType: String, trustSystemCerts: Boolean, appInForeground: StateFlow<Boolean>?): Boolean {
override fun isTrusted(chain: Array<X509Certificate>, authType: String, trustSystemCerts: Boolean, appInForeground: Boolean?): Boolean {
if (chain.isEmpty())
throw IllegalArgumentException("Certificate chain must not be empty")
val cert = chain[0]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/*
* 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.demo

import android.Manifest
Expand Down Expand Up @@ -33,6 +43,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import at.bitfire.cert4android.CustomCertManager
import at.bitfire.cert4android.CustomCertStore
import at.bitfire.cert4android.SettingsProvider
import at.bitfire.cert4android.ThemeManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -62,7 +73,7 @@ class MainActivity : ComponentActivity() {
.padding(8.dp)
.verticalScroll(rememberScrollState())) {
Row {
Checkbox(model.appInForeground.collectAsState().value, onCheckedChange = { foreground ->
Checkbox(model.foreground.collectAsState().value, onCheckedChange = { foreground ->
model.setInForeground(foreground)
})
Text("App in foreground")
Expand Down Expand Up @@ -131,7 +142,8 @@ class MainActivity : ComponentActivity() {
val context: Context
get() = getApplication()

val appInForeground = MutableStateFlow(true)
val foreground = MutableStateFlow(true)

val resultMessage = MutableLiveData<String>()

init {
Expand All @@ -145,7 +157,7 @@ class MainActivity : ComponentActivity() {
}

fun setInForeground(foreground: Boolean) {
appInForeground.value = foreground
this.foreground.value = foreground
}

fun testAccess(url: String, trustSystemCerts: Boolean = true) = viewModelScope.launch(Dispatchers.IO) {
Expand Down Expand Up @@ -177,8 +189,12 @@ class MainActivity : ComponentActivity() {
// set cert4android TrustManager and HostnameVerifier
val certManager = CustomCertManager(
certStore = CustomCertStore.getInstance(context),
trustSystemCerts = trustSystemCerts,
appInForeground = appInForeground
settings = object : SettingsProvider {
override val appInForeground
get() = foreground.value
override val trustSystemCerts
get() = trustSystemCerts
}
)

val sslContext = SSLContext.getInstance("TLS")
Expand Down
Loading