Skip to content

Commit 46cdd7a

Browse files
authored
[WIP] Conscrypt crash on okhttp cache (#73)
* Add CacheControl to MainActivity and OkhttpTest - Add CacheControl.FORCE_NETWORK to MainActivity - Update CustomCertManager to return emptyArray - Create new OkhttpTest with CacheControl * Initialize Conscrypt * Fix Conscrypt crash with OkHttp cache - Update CustomCertManager to use CustomCertStore - Add tests for BasicCertificateChainCleaner - Fix Conscrypt initialization and logging
1 parent 74c9c17 commit 46cdd7a

File tree

7 files changed

+121
-52
lines changed

7 files changed

+121
-52
lines changed

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

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package at.bitfire.cert4android
22

33
import org.junit.Before
44
import org.junit.Test
5-
import java.security.cert.CertificateFactory
6-
import java.security.cert.X509Certificate
75

86
class ConscryptTest {
97

@@ -20,50 +18,11 @@ class ConscryptTest {
2018
*/
2119
@Test
2220
fun test_X509Certificate_toString() {
23-
val certFactory = CertificateFactory.getInstance("X.509")
24-
val testCert = certFactory.generateCertificate(RAW_EXPIRED_CERT.byteInputStream()) as X509Certificate
21+
val testCert = TestCertificates.crashCert()
2522

2623
// Crashes with Conscrypt 2.5.3
2724
// Uncomment with Conscrypt >2.5.3
2825
// System.err.println(testCert.toString())
2926
}
3027

31-
32-
companion object {
33-
34-
const val RAW_EXPIRED_CERT = "-----BEGIN CERTIFICATE-----\n" +
35-
"MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv\n" +
36-
"MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk\n" +
37-
"ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF\n" +
38-
"eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow\n" +
39-
"gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO\n" +
40-
"BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD\n" +
41-
"VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq\n" +
42-
"hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw\n" +
43-
"AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6\n" +
44-
"2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr\n" +
45-
"ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt\n" +
46-
"4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq\n" +
47-
"m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/\n" +
48-
"vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT\n" +
49-
"8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE\n" +
50-
"IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO\n" +
51-
"KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO\n" +
52-
"GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/\n" +
53-
"s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g\n" +
54-
"JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD\n" +
55-
"AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9\n" +
56-
"MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy\n" +
57-
"bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6\n" +
58-
"Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ\n" +
59-
"zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj\n" +
60-
"Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY\n" +
61-
"Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5\n" +
62-
"B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx\n" +
63-
"PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR\n" +
64-
"pu/xO28QOG8=\n" +
65-
"-----END CERTIFICATE-----\n"
66-
67-
}
68-
6928
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
package at.bitfire.cert4android
66

77
import androidx.test.platform.app.InstrumentationRegistry
8-
import org.junit.Assert.*
8+
import org.junit.Assert.assertEquals
9+
import org.junit.Assert.assertFalse
10+
import org.junit.Assert.assertTrue
911
import org.junit.Before
1012
import org.junit.Test
1113

@@ -14,7 +16,7 @@ class CustomCertStoreTest {
1416
val context = InstrumentationRegistry.getInstrumentation().targetContext
1517
val certStore = CustomCertStore.getInstance(context)
1618

17-
val testCert = TestCertificates.testCert
19+
val testCert = TestCertificates.testCert()
1820

1921
@Before
2022
fun clearKeys() {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package at.bitfire.cert4android
2+
3+
import androidx.test.platform.app.InstrumentationRegistry
4+
import okhttp3.Cache
5+
import okhttp3.CacheControl
6+
import okhttp3.OkHttpClient
7+
import okhttp3.Request
8+
import okhttp3.internal.tls.BasicCertificateChainCleaner
9+
import okhttp3.internal.tls.BasicTrustRootIndex
10+
import okhttp3.internal.tls.OkHostnameVerifier
11+
import org.junit.Assert.assertEquals
12+
import org.junit.Test
13+
import javax.net.ssl.SSLContext
14+
15+
class OkhttpTest {
16+
17+
private val context by lazy { InstrumentationRegistry.getInstrumentation().targetContext }
18+
19+
init {
20+
ConscryptIntegration.initialize()
21+
}
22+
23+
@Test
24+
fun testAccessICloudComWithCache() {
25+
// See https://github.com/bitfireAT/davx5/issues/713 and
26+
// https://github.com/bitfireAT/cert4android/issues/72
27+
28+
val client = buildClient(
29+
useCache = false // CRASHES when true!
30+
)
31+
32+
// access sample URL
33+
val call = client.newCall(
34+
Request.Builder()
35+
.get()
36+
.cacheControl(CacheControl.FORCE_NETWORK) // don't retrieve from cache, the problem is storing to cache
37+
.url("https://icloud.com")
38+
.build()
39+
)
40+
call.execute().use { response ->
41+
assertEquals(200, response.code)
42+
}
43+
}
44+
45+
@Test
46+
fun testBasicCertificateChainCleaner() {
47+
val cleaner = BasicCertificateChainCleaner(BasicTrustRootIndex())
48+
49+
// See https://github.com/bitfireAT/cert4android/issues/72
50+
// CRASHES with Conscrypt 2.5.3:
51+
// cleaner.clean(listOf(TestCertificates.crashCert()), "doesn't matter")
52+
53+
// This is relevant, because okhttp creates such a BasicCertificateChainManager
54+
// when using a custom X509TrustManager. However when the trust manager extends
55+
// X509ExtendedTrustManager, AndroidCertificateChainManager is used on Android.
56+
}
57+
58+
59+
fun buildClient(useCache: Boolean): OkHttpClient {
60+
val builder = OkHttpClient.Builder()
61+
62+
// set cert4android TrustManager and HostnameVerifier
63+
val certManager = CustomCertManager(
64+
context,
65+
trustSystemCerts = true,
66+
appInForeground = null
67+
)
68+
69+
val sslContext = SSLContext.getInstance("TLS")
70+
sslContext.init(
71+
/* km = */ null,
72+
/* tm = */ arrayOf(certManager),
73+
/* random = */ null
74+
)
75+
builder
76+
.sslSocketFactory(sslContext.socketFactory, certManager)
77+
.hostnameVerifier(certManager.HostnameVerifier(OkHostnameVerifier))
78+
79+
if (useCache)
80+
builder.cache(Cache(context.cacheDir, 10 * 1024 * 1024))
81+
82+
return builder.build()
83+
}
84+
85+
}

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

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,34 @@ import javax.net.ssl.X509TrustManager
1313
*/
1414
object TestCertificates {
1515

16-
init {
17-
ConscryptIntegration.initialize()
18-
}
19-
2016
val certFactory = CertificateFactory.getInstance("X.509")
2117

18+
/* generated for testing */
19+
const val RANDOM_UNSIGNED_CERT = "-----BEGIN CERTIFICATE-----\n" +
20+
"MIIDiTCCAnGgAwIBAgIUSGSlBGowPbzWvRWkulK56y/8di8wDQYJKoZIhvcNAQEL\n" +
21+
"BQAwbTELMAkGA1UEBhMCWFgxDTALBgNVBAgMBFRFU1QxDTALBgNVBAcMBFRFU1Qx\n" +
22+
"DTALBgNVBAoMBFRFU1QxDTALBgNVBAsMBFRFU1QxDTALBgNVBAMMBFRFU1QxEzAR\n" +
23+
"BgkqhkiG9w0BCQEWBFRFU1QwHhcNMjUxMDMxMTUzMTMzWhcNMjYxMDMxMTUzMTMz\n" +
24+
"WjBtMQswCQYDVQQGEwJYWDENMAsGA1UECAwEVEVTVDENMAsGA1UEBwwEVEVTVDEN\n" +
25+
"MAsGA1UECgwEVEVTVDENMAsGA1UECwwEVEVTVDENMAsGA1UEAwwEVEVTVDETMBEG\n" +
26+
"CSqGSIb3DQEJARYEVEVTVDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" +
27+
"AJ3MQAseGNujzTmItMHiOoQaC2+ss4KG2eAIF4ZVMSsvWVhIRZRPlabpX2FKhTRf\n" +
28+
"XfmbXI0ISYiq81oo8Nv+AETWwFWPPmPxrJDKrXNQKMN33Anm2bCHTsl6K3eBiMZt\n" +
29+
"gnSfW4B80r8jvsj4HNoXfR8/RyEG5mTqE35djL3Z8gDH5wHnaoxRqVRtkzFfj9M7\n" +
30+
"VJf7Ifbun5rjBx8XkrC6R02vw465K/UNQ7dyBWA68OI7luL8Kg3lqioovHqJv9I0\n" +
31+
"iD0sb8P82kJuwFzWVRv/hgBTcdX8xC8J6j6pGwJ/nw/tBHTEHGSOhzeqxjQ5Xicr\n" +
32+
"Z13C8zjmYywm1A4Xw+MY/mECAwEAAaMhMB8wHQYDVR0OBBYEFFtIe8VoYQlz/LJ8\n" +
33+
"DrU+eDOQlBmRMA0GCSqGSIb3DQEBCwUAA4IBAQB2UDqHpvhnenTA6zykz4ThQ40y\n" +
34+
"6+V4Koa8bdCdAtRZEgz1ZsOntaQ/PBFegd2ksp2QpF0T868ON6/toYy9PTdQ0wd9\n" +
35+
"X9p3Co+9iV5/RHGlt8YOQyljto8fJ+V4kGv4VrEecYS/+/nQtqR3IKO2gaPol0w8\n" +
36+
"SxmO346ku+O5ifupJrj5Wy8CYe9OJSKdYOd/Qpgbh9ecwmL1Lq8KRJqB2gzocCxg\n" +
37+
"32PUB1ZgVmOQfKW14qb0dZhh0sJwGg2W7EFXPsfN+EFWEJqOkD0Rg3k1K8k5ZnX0\n" +
38+
"nbs2Hqf96XLIZxFOcJWxaIaDhexvRdJSJiCnWQL2kHH032aN+dQeSkLtQO9c\n" +
39+
"-----END CERTIFICATE-----\n\n"
40+
41+
fun crashCert() = certFactory.generateCertificate(RANDOM_UNSIGNED_CERT.byteInputStream()) as X509Certificate
42+
43+
/** some test certificate (untrusted Snakeoil certificate generated by Debian) */
2244
const val RAW_TEST_CERT = "-----BEGIN CERTIFICATE-----\n" +
2345
"MIICxzCCAa+gAwIBAgIUe7x8TfMqQlJ+qTF/L+n6NqRqKAwwDQYJKoZIhvcNAQEL\n" +
2446
"BQAwDjEMMAoGA1UEAwwDdG50MB4XDTIwMDIxODE5NTYyMFoXDTMwMDIxNTE5NTYy\n" +
@@ -37,8 +59,7 @@ object TestCertificates {
3759
"46rF2aPRcVr71DWqbV+YdwkI3N7EwZOIIEl6a9srF+q01LrIukWkScuU9Q==\n" +
3860
"-----END CERTIFICATE-----\n"
3961

40-
/** some test certificate (untrusted Snakeoil certificate generated by Debian) */
41-
val testCert = certFactory.generateCertificate(RAW_TEST_CERT.byteInputStream()) as X509Certificate
62+
fun testCert() = certFactory.generateCertificate(RAW_TEST_CERT.byteInputStream()) as X509Certificate
4263

4364

4465
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class UserDecisionRegistryTest {
2929
private val certStore = CustomCertStore.getInstance(context)
3030
private val registry = UserDecisionRegistry.getInstance(context)
3131

32-
private val testCert = TestCertificates.testCert
32+
private val testCert = TestCertificates.testCert()
3333

3434

3535
@Before

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class CustomCertManager @JvmOverloads constructor(
5555
throw CertificateException("Certificate chain not trusted")
5656
}
5757

58-
override fun getAcceptedIssuers() = arrayOf<X509Certificate>()
58+
override fun getAcceptedIssuers() = emptyArray<X509Certificate>()
5959

6060

6161
/**

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import at.bitfire.cert4android.ThemeManager
3737
import kotlinx.coroutines.Dispatchers
3838
import kotlinx.coroutines.flow.MutableStateFlow
3939
import kotlinx.coroutines.launch
40+
import okhttp3.CacheControl
4041
import okhttp3.OkHttpClient
4142
import okhttp3.Request
4243
import okhttp3.internal.tls.OkHostnameVerifier
@@ -155,6 +156,7 @@ class MainActivity : ComponentActivity() {
155156
val call = client.newCall(
156157
Request.Builder()
157158
.get()
159+
.cacheControl(CacheControl.FORCE_NETWORK)
158160
.url(url)
159161
.build()
160162
)

0 commit comments

Comments
 (0)