Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide callback instead of doing UI tasks of calling app #49 #50

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
16 changes: 8 additions & 8 deletions lib/src/main/java/at/bitfire/cert4android/CustomCertManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package at.bitfire.cert4android

import android.annotation.SuppressLint
import android.content.Context
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.CoroutineScope
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import javax.net.ssl.SSLSession
Expand All @@ -18,18 +18,18 @@ import javax.net.ssl.X509TrustManager
* Initializes Conscrypt when it is first loaded.
*
* @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 getUserDecision anonymous function to retrieve user decision on whether to trust a
* certificate; should return *true* if the user trusts the certificate
*/
@SuppressLint("CustomX509TrustManager")
class CustomCertManager @JvmOverloads constructor(
context: Context,
scope: CoroutineScope,
val trustSystemCerts: Boolean = true,
var appInForeground: StateFlow<Boolean>?
private val getUserDecision: suspend (X509Certificate) -> Boolean
): X509TrustManager {

val certStore = CustomCertStore.getInstance(context)
val certStore = CustomCertStore.getInstance(context, scope)


@Throws(CertificateException::class)
Expand All @@ -47,7 +47,7 @@ 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, getUserDecision))
throw CertificateException("Certificate chain not trusted")
}

Expand All @@ -71,7 +71,7 @@ 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", false, getUserDecision))
return true
}

Expand Down
23 changes: 12 additions & 11 deletions lib/src/main/java/at/bitfire/cert4android/CustomCertStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ package at.bitfire.cert4android
import android.annotation.SuppressLint
import android.content.Context
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.conscrypt.Conscrypt
Expand All @@ -22,6 +22,7 @@ import java.util.logging.Level

class CustomCertStore internal constructor(
private val context: Context,
private val scope: CoroutineScope,
private val userTimeout: Long = 60000L
) {

Expand All @@ -34,12 +35,12 @@ class CustomCertStore internal constructor(
private var instance: CustomCertStore? = null

@Synchronized
fun getInstance(context: Context): CustomCertStore {
fun getInstance(context: Context, scope: CoroutineScope): CustomCertStore {
instance?.let {
return it
}

val newInstance = CustomCertStore(context.applicationContext)
val newInstance = CustomCertStore(context.applicationContext, scope)
instance = newInstance
return newInstance
}
Expand Down Expand Up @@ -77,7 +78,12 @@ class CustomCertStore internal constructor(
/**
* 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,
getUserDecision: suspend (X509Certificate) -> Boolean
): Boolean {
if (chain.isEmpty())
throw IllegalArgumentException("Certificate chain must not be empty")
val cert = chain[0]
Expand All @@ -103,17 +109,12 @@ class CustomCertStore internal constructor(
}
}

if (appInForeground == null) {
Cert4Android.log.log(Level.INFO, "Certificate not known and running in non-interactive mode, rejecting")
return false
}

return runBlocking {
val ui = UserDecisionRegistry.getInstance(context)
val ui = UserDecisionRegistry.getInstance(context, scope)

try {
withTimeout(userTimeout) {
ui.check(cert, appInForeground.value)
ui.check(cert, getUserDecision)
}
} catch (_: TimeoutCancellationException) {
Cert4Android.log.log(Level.WARNING, "User timeout while waiting for certificate decision, rejecting")
Expand Down
Loading
Loading