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 @@ -2,8 +2,6 @@ package at.bitfire.cert4android

import org.junit.Before
import org.junit.Test
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate

class ConscryptTest {

Expand All @@ -20,50 +18,11 @@ class ConscryptTest {
*/
@Test
fun test_X509Certificate_toString() {
val certFactory = CertificateFactory.getInstance("X.509")
val testCert = certFactory.generateCertificate(RAW_EXPIRED_CERT.byteInputStream()) as X509Certificate
val testCert = TestCertificates.crashCert()

// Crashes with Conscrypt 2.5.3
// Uncomment with Conscrypt >2.5.3
// System.err.println(testCert.toString())
}


companion object {

const val RAW_EXPIRED_CERT = "-----BEGIN CERTIFICATE-----\n" +
"MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv\n" +
"MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk\n" +
"ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF\n" +
"eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow\n" +
"gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO\n" +
"BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD\n" +
"VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq\n" +
"hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw\n" +
"AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6\n" +
"2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr\n" +
"ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt\n" +
"4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq\n" +
"m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/\n" +
"vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT\n" +
"8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE\n" +
"IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO\n" +
"KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO\n" +
"GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/\n" +
"s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g\n" +
"JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD\n" +
"AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9\n" +
"MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy\n" +
"bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6\n" +
"Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ\n" +
"zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj\n" +
"Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY\n" +
"Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5\n" +
"B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx\n" +
"PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR\n" +
"pu/xO28QOG8=\n" +
"-----END CERTIFICATE-----\n"

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package at.bitfire.cert4android

import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

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

val testCert = TestCertificates.testCert
val testCert = TestCertificates.testCert()

@Before
fun clearKeys() {
Expand Down
85 changes: 85 additions & 0 deletions lib/src/androidTest/java/at/bitfire/cert4android/OkhttpTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package at.bitfire.cert4android

import androidx.test.platform.app.InstrumentationRegistry
import okhttp3.Cache
import okhttp3.CacheControl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.internal.tls.BasicCertificateChainCleaner
import okhttp3.internal.tls.BasicTrustRootIndex
import okhttp3.internal.tls.OkHostnameVerifier
import org.junit.Assert.assertEquals
import org.junit.Test
import javax.net.ssl.SSLContext

class OkhttpTest {

private val context by lazy { InstrumentationRegistry.getInstrumentation().targetContext }

init {
ConscryptIntegration.initialize()
}

@Test
fun testAccessICloudComWithCache() {
// See https://github.com/bitfireAT/davx5/issues/713 and
// https://github.com/bitfireAT/cert4android/issues/72

val client = buildClient(
useCache = false // CRASHES when true!
)

// access sample URL
val call = client.newCall(
Request.Builder()
.get()
.cacheControl(CacheControl.FORCE_NETWORK) // don't retrieve from cache, the problem is storing to cache
.url("https://icloud.com")
.build()
)
call.execute().use { response ->
assertEquals(200, response.code)
}
}

@Test
fun testBasicCertificateChainCleaner() {
val cleaner = BasicCertificateChainCleaner(BasicTrustRootIndex())

// See https://github.com/bitfireAT/cert4android/issues/72
// CRASHES with Conscrypt 2.5.3:
// cleaner.clean(listOf(TestCertificates.crashCert()), "doesn't matter")

// This is relevant, because okhttp creates such a BasicCertificateChainManager
// when using a custom X509TrustManager. However when the trust manager extends
// X509ExtendedTrustManager, AndroidCertificateChainManager is used on Android.
}


fun buildClient(useCache: Boolean): OkHttpClient {
val builder = OkHttpClient.Builder()

// set cert4android TrustManager and HostnameVerifier
val certManager = CustomCertManager(
context,
trustSystemCerts = true,
appInForeground = null
)

val sslContext = SSLContext.getInstance("TLS")
sslContext.init(
/* km = */ null,
/* tm = */ arrayOf(certManager),
/* random = */ null
)
builder
.sslSocketFactory(sslContext.socketFactory, certManager)
.hostnameVerifier(certManager.HostnameVerifier(OkHostnameVerifier))

if (useCache)
builder.cache(Cache(context.cacheDir, 10 * 1024 * 1024))

return builder.build()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,34 @@ import javax.net.ssl.X509TrustManager
*/
object TestCertificates {

init {
ConscryptIntegration.initialize()
}

val certFactory = CertificateFactory.getInstance("X.509")

/* generated for testing */
const val RANDOM_UNSIGNED_CERT = "-----BEGIN CERTIFICATE-----\n" +
"MIIDiTCCAnGgAwIBAgIUSGSlBGowPbzWvRWkulK56y/8di8wDQYJKoZIhvcNAQEL\n" +
"BQAwbTELMAkGA1UEBhMCWFgxDTALBgNVBAgMBFRFU1QxDTALBgNVBAcMBFRFU1Qx\n" +
"DTALBgNVBAoMBFRFU1QxDTALBgNVBAsMBFRFU1QxDTALBgNVBAMMBFRFU1QxEzAR\n" +
"BgkqhkiG9w0BCQEWBFRFU1QwHhcNMjUxMDMxMTUzMTMzWhcNMjYxMDMxMTUzMTMz\n" +
"WjBtMQswCQYDVQQGEwJYWDENMAsGA1UECAwEVEVTVDENMAsGA1UEBwwEVEVTVDEN\n" +
"MAsGA1UECgwEVEVTVDENMAsGA1UECwwEVEVTVDENMAsGA1UEAwwEVEVTVDETMBEG\n" +
"CSqGSIb3DQEJARYEVEVTVDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" +
"AJ3MQAseGNujzTmItMHiOoQaC2+ss4KG2eAIF4ZVMSsvWVhIRZRPlabpX2FKhTRf\n" +
"XfmbXI0ISYiq81oo8Nv+AETWwFWPPmPxrJDKrXNQKMN33Anm2bCHTsl6K3eBiMZt\n" +
"gnSfW4B80r8jvsj4HNoXfR8/RyEG5mTqE35djL3Z8gDH5wHnaoxRqVRtkzFfj9M7\n" +
"VJf7Ifbun5rjBx8XkrC6R02vw465K/UNQ7dyBWA68OI7luL8Kg3lqioovHqJv9I0\n" +
"iD0sb8P82kJuwFzWVRv/hgBTcdX8xC8J6j6pGwJ/nw/tBHTEHGSOhzeqxjQ5Xicr\n" +
"Z13C8zjmYywm1A4Xw+MY/mECAwEAAaMhMB8wHQYDVR0OBBYEFFtIe8VoYQlz/LJ8\n" +
"DrU+eDOQlBmRMA0GCSqGSIb3DQEBCwUAA4IBAQB2UDqHpvhnenTA6zykz4ThQ40y\n" +
"6+V4Koa8bdCdAtRZEgz1ZsOntaQ/PBFegd2ksp2QpF0T868ON6/toYy9PTdQ0wd9\n" +
"X9p3Co+9iV5/RHGlt8YOQyljto8fJ+V4kGv4VrEecYS/+/nQtqR3IKO2gaPol0w8\n" +
"SxmO346ku+O5ifupJrj5Wy8CYe9OJSKdYOd/Qpgbh9ecwmL1Lq8KRJqB2gzocCxg\n" +
"32PUB1ZgVmOQfKW14qb0dZhh0sJwGg2W7EFXPsfN+EFWEJqOkD0Rg3k1K8k5ZnX0\n" +
"nbs2Hqf96XLIZxFOcJWxaIaDhexvRdJSJiCnWQL2kHH032aN+dQeSkLtQO9c\n" +
"-----END CERTIFICATE-----\n\n"

fun crashCert() = certFactory.generateCertificate(RANDOM_UNSIGNED_CERT.byteInputStream()) as X509Certificate

/** some test certificate (untrusted Snakeoil certificate generated by Debian) */
const val RAW_TEST_CERT = "-----BEGIN CERTIFICATE-----\n" +
"MIICxzCCAa+gAwIBAgIUe7x8TfMqQlJ+qTF/L+n6NqRqKAwwDQYJKoZIhvcNAQEL\n" +
"BQAwDjEMMAoGA1UEAwwDdG50MB4XDTIwMDIxODE5NTYyMFoXDTMwMDIxNTE5NTYy\n" +
Expand All @@ -37,8 +59,7 @@ object TestCertificates {
"46rF2aPRcVr71DWqbV+YdwkI3N7EwZOIIEl6a9srF+q01LrIukWkScuU9Q==\n" +
"-----END CERTIFICATE-----\n"

/** some test certificate (untrusted Snakeoil certificate generated by Debian) */
val testCert = certFactory.generateCertificate(RAW_TEST_CERT.byteInputStream()) as X509Certificate
fun testCert() = certFactory.generateCertificate(RAW_TEST_CERT.byteInputStream()) as X509Certificate


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class UserDecisionRegistryTest {
private val certStore = CustomCertStore.getInstance(context)
private val registry = UserDecisionRegistry.getInstance(context)

private val testCert = TestCertificates.testCert
private val testCert = TestCertificates.testCert()


@Before
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class CustomCertManager @JvmOverloads constructor(
throw CertificateException("Certificate chain not trusted")
}

override fun getAcceptedIssuers() = arrayOf<X509Certificate>()
override fun getAcceptedIssuers() = emptyArray<X509Certificate>()


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import at.bitfire.cert4android.ThemeManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import okhttp3.CacheControl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.internal.tls.OkHostnameVerifier
Expand Down Expand Up @@ -155,6 +156,7 @@ class MainActivity : ComponentActivity() {
val call = client.newCall(
Request.Builder()
.get()
.cacheControl(CacheControl.FORCE_NETWORK)
.url(url)
.build()
)
Expand Down
Loading