Skip to content

Commit 75cc691

Browse files
authored
Add SettingsProvider interface for settings (#96)
* Add `SettingsProvider` interface for settings - Modify `CustomCertManager` to use `SettingsProvider` for settings - Remove `Flow` for setting that can change * KDoc * Refactor SettingsProvider methods to properties - Change `appInForeground()` to `appInForeground` property - Change `trustSystemCerts()` to `trustSystemCerts` property - Update usages in CustomCertManager and MainActivity * Fix tests * Fix tests (2)
1 parent ef5917f commit 75cc691

8 files changed

Lines changed: 91 additions & 25 deletions

File tree

lib/src/androidTest/java/at/bitfire/cert4android/OkhttpTest.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ class OkhttpTest {
5959
// set cert4android TrustManager and HostnameVerifier
6060
val certManager = CustomCertManager(
6161
CustomCertStore.getInstance(context),
62-
trustSystemCerts = true,
63-
appInForeground = null
62+
object : SettingsProvider {
63+
override val appInForeground = null
64+
override val trustSystemCerts = true
65+
}
6466
)
6567

6668
val sslContext = SSLContext.getInstance("TLS")

lib/src/main/java/at/bitfire/cert4android/CertStore.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
package at.bitfire.cert4android
1212

13-
import kotlinx.coroutines.flow.StateFlow
1413
import java.security.cert.X509Certificate
1514

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

2827
/**
2928
* Determines whether a certificate has been explicitly accepted by the user. In this case,

lib/src/main/java/at/bitfire/cert4android/CustomCertManager.kt

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
package at.bitfire.cert4android
1212

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

3632
private val logger
@@ -51,7 +47,13 @@ class CustomCertManager @JvmOverloads constructor(
5147
*/
5248
@Throws(CertificateException::class)
5349
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
54-
if (!certStore.isTrusted(chain, authType, trustSystemCerts, appInForeground))
50+
if (!certStore.isTrusted(
51+
chain,
52+
authType,
53+
trustSystemCerts = settings.trustSystemCerts,
54+
appInForeground = settings.appInForeground
55+
)
56+
)
5557
throw CertificateException("Certificate chain not trusted")
5658
}
5759

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

lib/src/main/java/at/bitfire/cert4android/CustomCertStore.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import android.annotation.SuppressLint
1414
import android.content.Context
1515
import androidx.annotation.VisibleForTesting
1616
import kotlinx.coroutines.TimeoutCancellationException
17-
import kotlinx.coroutines.flow.StateFlow
1817
import kotlinx.coroutines.runBlocking
1918
import kotlinx.coroutines.withTimeout
2019
import java.io.File
@@ -75,7 +74,7 @@ class CustomCertStore internal constructor(
7574
/**
7675
* Determines whether a certificate chain is trusted.
7776
*/
78-
override fun isTrusted(chain: Array<X509Certificate>, authType: String, trustSystemCerts: Boolean, appInForeground: StateFlow<Boolean>?): Boolean {
77+
override fun isTrusted(chain: Array<X509Certificate>, authType: String, trustSystemCerts: Boolean, appInForeground: Boolean?): Boolean {
7978
if (chain.isEmpty())
8079
throw IllegalArgumentException("Certificate chain must not be empty")
8180
val cert = chain[0]
@@ -111,7 +110,7 @@ class CustomCertStore internal constructor(
111110

112111
try {
113112
withTimeout(userTimeout) {
114-
ui.check(cert, appInForeground.value)
113+
ui.check(cert, appInForeground)
115114
}
116115
} catch (_: TimeoutCancellationException) {
117116
logger.log(Level.WARNING, "User timeout while waiting for certificate decision, rejecting")
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* SPDX-License-Identifier: MPL-2.0
9+
*/
10+
11+
package at.bitfire.cert4android
12+
13+
/**
14+
* Provides settings for cert4android. Implementations can override the getters.
15+
*
16+
* Usually implemented by the app which uses cert4android, and then passed to cert4android classes
17+
* which need it.
18+
*/
19+
interface SettingsProvider {
20+
21+
/**
22+
* The app foreground status:
23+
*
24+
* - `true`: foreground – directly launch UI ([TrustCertificateActivity]) and show notification (if possible)
25+
* - `false`: background – only show notification (if possible)
26+
* - `null`: non-interactive mode – don't show notification or launch activity
27+
*/
28+
val appInForeground: Boolean?
29+
30+
/**
31+
* Whether system certificates shall be trusted.
32+
*
33+
* @return `true` if system certificates are considered trustworthy, `false` otherwise
34+
*/
35+
val trustSystemCerts: Boolean
36+
37+
}

lib/src/test/java/at/bitfire/cert4android/CustomCertManagerTest.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,14 @@ class CustomCertManagerTest {
3636
@Before
3737
fun createCertManager() {
3838
certStore = TestCertStore()
39-
certManager = CustomCertManager(certStore, true, null)
40-
paranoidCertManager = CustomCertManager(certStore, false, null)
39+
certManager = CustomCertManager(certStore, object : SettingsProvider {
40+
override val appInForeground = null
41+
override val trustSystemCerts = true
42+
})
43+
paranoidCertManager = CustomCertManager(certStore, object : SettingsProvider {
44+
override val appInForeground = null
45+
override val trustSystemCerts = false
46+
})
4147
}
4248

4349

lib/src/test/java/at/bitfire/cert4android/TestCertStore.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
package at.bitfire.cert4android
1212

1313
import androidx.annotation.VisibleForTesting
14-
import kotlinx.coroutines.flow.StateFlow
1514
import java.security.cert.X509Certificate
1615
import java.util.logging.Level
1716
import java.util.logging.Logger
@@ -43,7 +42,7 @@ class TestCertStore: CertStore {
4342
/**
4443
* Determines whether a certificate chain is trusted.
4544
*/
46-
override fun isTrusted(chain: Array<X509Certificate>, authType: String, trustSystemCerts: Boolean, appInForeground: StateFlow<Boolean>?): Boolean {
45+
override fun isTrusted(chain: Array<X509Certificate>, authType: String, trustSystemCerts: Boolean, appInForeground: Boolean?): Boolean {
4746
if (chain.isEmpty())
4847
throw IllegalArgumentException("Certificate chain must not be empty")
4948
val cert = chain[0]

sample-app/src/main/java/at/bitfire/cert4android/demo/MainActivity.kt

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
/*
2+
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* SPDX-License-Identifier: MPL-2.0
9+
*/
10+
111
package at.bitfire.cert4android.demo
212

313
import android.Manifest
@@ -33,6 +43,7 @@ import androidx.lifecycle.MutableLiveData
3343
import androidx.lifecycle.viewModelScope
3444
import at.bitfire.cert4android.CustomCertManager
3545
import at.bitfire.cert4android.CustomCertStore
46+
import at.bitfire.cert4android.SettingsProvider
3647
import at.bitfire.cert4android.ThemeManager
3748
import kotlinx.coroutines.Dispatchers
3849
import kotlinx.coroutines.flow.MutableStateFlow
@@ -62,7 +73,7 @@ class MainActivity : ComponentActivity() {
6273
.padding(8.dp)
6374
.verticalScroll(rememberScrollState())) {
6475
Row {
65-
Checkbox(model.appInForeground.collectAsState().value, onCheckedChange = { foreground ->
76+
Checkbox(model.foreground.collectAsState().value, onCheckedChange = { foreground ->
6677
model.setInForeground(foreground)
6778
})
6879
Text("App in foreground")
@@ -131,7 +142,8 @@ class MainActivity : ComponentActivity() {
131142
val context: Context
132143
get() = getApplication()
133144

134-
val appInForeground = MutableStateFlow(true)
145+
val foreground = MutableStateFlow(true)
146+
135147
val resultMessage = MutableLiveData<String>()
136148

137149
init {
@@ -145,7 +157,7 @@ class MainActivity : ComponentActivity() {
145157
}
146158

147159
fun setInForeground(foreground: Boolean) {
148-
appInForeground.value = foreground
160+
this.foreground.value = foreground
149161
}
150162

151163
fun testAccess(url: String, trustSystemCerts: Boolean = true) = viewModelScope.launch(Dispatchers.IO) {
@@ -177,8 +189,12 @@ class MainActivity : ComponentActivity() {
177189
// set cert4android TrustManager and HostnameVerifier
178190
val certManager = CustomCertManager(
179191
certStore = CustomCertStore.getInstance(context),
180-
trustSystemCerts = trustSystemCerts,
181-
appInForeground = appInForeground
192+
settings = object : SettingsProvider {
193+
override val appInForeground
194+
get() = foreground.value
195+
override val trustSystemCerts
196+
get() = trustSystemCerts
197+
}
182198
)
183199

184200
val sslContext = SSLContext.getInstance("TLS")

0 commit comments

Comments
 (0)