diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 47b8b01..e143410 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ dokka = "2.0.0" junit = "4.13.2" kotlin = "2.2.0" mockk = "1.14.4" -okhttp3 = "5.1.0" +okhttp = "5.3.0" [libraries] androidx-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } @@ -36,7 +36,8 @@ conscrypt = { module = "org.conscrypt:conscrypt-android", version.ref = "conscry junit = { module = "junit:junit", version.ref = "junit" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" } -okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp3" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } +okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver3", version.ref = "okhttp" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index c4d04d3..3f46942 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -67,6 +67,7 @@ android { packaging { resources { excludes.add("META-INF/*.md") + excludes.add("META-INF/versions/*/OSGI-INF/MANIFEST.MF") } } diff --git a/sample-app/build.gradle.kts b/sample-app/build.gradle.kts index e645402..9a23d8b 100644 --- a/sample-app/build.gradle.kts +++ b/sample-app/build.gradle.kts @@ -43,10 +43,9 @@ android { dependencies { implementation(libs.androidx.core) implementation(libs.androidx.lifecycle.runtime) - implementation(libs.androidx.appcompat) - implementation(libs.androidx.activityCompose) + implementation(platform(libs.compose.bom)) implementation(libs.compose.material3) implementation(libs.compose.ui.base) @@ -54,5 +53,7 @@ dependencies { debugImplementation(libs.compose.ui.toolingPreview) implementation(libs.compose.runtime.livedata) + implementation(libs.okhttp) + implementation(project(":lib")) } \ No newline at end of file diff --git a/sample-app/src/main/java/at/bitfire/cert4android/demo/MainActivity.kt b/sample-app/src/main/java/at/bitfire/cert4android/demo/MainActivity.kt index d2dea04..d726196 100644 --- a/sample-app/src/main/java/at/bitfire/cert4android/demo/MainActivity.kt +++ b/sample-app/src/main/java/at/bitfire/cert4android/demo/MainActivity.kt @@ -3,8 +3,8 @@ package at.bitfire.cert4android.demo import android.Manifest import android.annotation.SuppressLint import android.app.Application +import android.content.Context import android.content.pm.PackageManager -import android.net.SSLCertificateSocketFactory import android.os.Build import android.os.Bundle import android.util.Log @@ -37,10 +37,12 @@ import at.bitfire.cert4android.ThemeManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.internal.tls.OkHostnameVerifier import org.apache.http.conn.ssl.AllowAllHostnameVerifier -import org.apache.http.conn.ssl.StrictHostnameVerifier -import java.net.URL import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.SSLContext class MainActivity : ComponentActivity() { @@ -66,15 +68,15 @@ class MainActivity : ComponentActivity() { } Button(onClick = { - model.testAccess("https://www.github.com") + model.testAccess("https://icloud.com") }, modifier = Modifier.padding(top = 16.dp)) { - Text("Access normal URL with trusted system certs") + Text("Access icloud.com with trusted system certs") } Button(onClick = { - model.testAccess("https://www.github.com", trustSystemCerts = false) + model.testAccess("https://icloud.com", trustSystemCerts = false) }, modifier = Modifier.padding(top = 16.dp)) { - Text("Access normal URL with distrusted system certs") + Text("Access icloud.com with distrusted system certs") } Button(onClick = { @@ -125,11 +127,8 @@ class MainActivity : ComponentActivity() { class Model(application: Application): AndroidViewModel(application) { - companion object { - - const val TAG = "cert4android.sample-app" - - } + val context: Context + get() = getApplication() val appInForeground = MutableStateFlow(true) val resultMessage = MutableLiveData() @@ -141,7 +140,7 @@ class MainActivity : ComponentActivity() { } fun reset() = viewModelScope.launch(Dispatchers.IO) { - CustomCertStore.getInstance(getApplication()).clearUserDecisions() + CustomCertStore.getInstance(context).clearUserDecisions() } fun setInForeground(foreground: Boolean) { @@ -150,31 +149,57 @@ class MainActivity : ComponentActivity() { fun testAccess(url: String, trustSystemCerts: Boolean = true) = viewModelScope.launch(Dispatchers.IO) { try { - val urlConn = URL(url).openConnection() as HttpsURLConnection + val client = buildClient(trustSystemCerts) - // set cert4android TrustManager and HostnameVerifier - val certMgr = CustomCertManager( - getApplication(), - trustSystemCerts = trustSystemCerts, - appInForeground = appInForeground + // access sample URL + val call = client.newCall( + Request.Builder() + .get() + .url(url) + .build() ) - urlConn.hostnameVerifier = certMgr.HostnameVerifier(StrictHostnameVerifier()) - urlConn.sslSocketFactory = object : SSLCertificateSocketFactory(/* handshakeTimeoutMillis = */ 1000) { - init { - setTrustManagers(arrayOf(certMgr)) - } + call.execute().use { response -> + // log result + Log.i(TAG, "testAccess(): HTTP ${response.code}") + resultMessage.postValue("${response.code} ${response.message}") } - - // access sample URL - Log.i(TAG, "testAccess(): HTTP ${urlConn.responseCode}") - resultMessage.postValue("${urlConn.responseCode} ${urlConn.responseMessage}") - urlConn.inputStream.close() } catch (e: Exception) { resultMessage.postValue("testAccess() ERROR: ${e.message}") Log.w(TAG, "testAccess(): ERROR: ${e.message}") } } + fun buildClient(trustSystemCerts: Boolean): OkHttpClient { + val builder = OkHttpClient.Builder() + + // set cert4android TrustManager and HostnameVerifier + val certManager = CustomCertManager( + context, + trustSystemCerts = trustSystemCerts, + appInForeground = appInForeground + ) + + val sslContext = SSLContext.getInstance("TLS") + sslContext.init( + /* km = */ null, + /* tm = */ arrayOf(certManager), + /* random = */ null + ) + builder + .sslSocketFactory(sslContext.socketFactory, certManager) + .hostnameVerifier(certManager.HostnameVerifier(OkHostnameVerifier)) + + // add cache + //builder.cache(Cache(context.cacheDir, 10 * 1024 * 1024)) + + return builder.build() + } + + } + + + companion object { + const val TAG = "cert4android.sample-app" } } \ No newline at end of file