Skip to content
Open
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
4 changes: 3 additions & 1 deletion nocodes/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,12 @@ dependencies {

api project(':sdk')

testImplementation 'junit:junit:4.13.2'

androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation "androidx.test:runner:1.5.2"
androidTestImplementation "androidx.test:rules:1.5.0"
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
}

apply from: "../scripts/maven-release.gradle"
apply from: "../scripts/maven-release.gradle"
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@ import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.MalformedURLException
import java.net.URL
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager

private const val NETWORK_ENCODING = "utf-8"

Expand Down Expand Up @@ -66,19 +60,6 @@ internal class NetworkClientImpl(
return try {
val connection = url.openConnection() as HttpURLConnection

// Trust all certificates for staging
if (connection is HttpsURLConnection) {
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
})
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, trustAllCerts, SecureRandom())
connection.sslSocketFactory = sslContext.socketFactory
connection.setHostnameVerifier { _, _ -> true }
}

// Set smart timeout based on fallback availability
val timeout = if (isFallbackAvailable) {
TimeoutConstants.FALLBACK_AVAILABLE_TIMEOUT
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.qonversion.nocodes.internal.networkLayer.networkClient

import io.qonversion.nocodes.internal.common.serializers.Serializer
import org.junit.Assert.assertSame
import org.junit.Test
import java.net.URL
import java.net.URLConnection
import java.net.URLStreamHandler
import java.security.Principal
import java.security.cert.Certificate
import javax.net.ssl.HttpsURLConnection

internal class NetworkClientImplTest {
@Test
fun `https connections retain platform TLS verification`() {
lateinit var platformConnection: TestHttpsURLConnection
val url = URL(null, "https://api.qonversion.io", object : URLStreamHandler() {
override fun openConnection(url: URL): URLConnection {
return TestHttpsURLConnection(url).also { platformConnection = it }
}
})
val platformSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory()
val platformHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier()

val connection = NetworkClientImpl(UnusedSerializer()).connect(url) as HttpsURLConnection

assertSame(platformConnection, connection)
assertSame(platformSocketFactory, connection.sslSocketFactory)
assertSame(platformHostnameVerifier, connection.hostnameVerifier)
}

private class UnusedSerializer : Serializer {
override fun serialize(data: Map<String, Any?>): String = error("not used")
override fun deserialize(payload: String): Any = error("not used")
}

private class TestHttpsURLConnection(url: URL) : HttpsURLConnection(url) {
override fun connect() = Unit
override fun disconnect() = Unit
override fun usingProxy(): Boolean = false
override fun getCipherSuite(): String = ""
override fun getLocalCertificates(): Array<Certificate>? = null
override fun getServerCertificates(): Array<Certificate> = emptyArray()
override fun getPeerPrincipal(): Principal? = null
override fun getLocalPrincipal(): Principal? = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,7 @@ import okhttp3.Cache
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager

@Module
internal class NetworkModule {
Expand Down Expand Up @@ -86,21 +81,11 @@ internal class NetworkModule {
context: Application,
interceptor: NetworkInterceptor
): OkHttpClient {
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
})
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, trustAllCerts, SecureRandom())

return OkHttpClient.Builder()
.cache(Cache(context.cacheDir, CACHE_SIZE))
.readTimeout(TIMEOUT, TimeUnit.SECONDS)
.connectTimeout(TIMEOUT, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true }
.build()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.qonversion.android.sdk.internal.di.module

import android.app.Application
import androidx.test.core.app.ApplicationProvider
import com.qonversion.android.sdk.internal.api.NetworkInterceptor
import io.mockk.mockk
import org.junit.Assert.assertFalse
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.lang.reflect.Proxy
import javax.net.ssl.SSLPeerUnverifiedException
import javax.net.ssl.SSLSession

@RunWith(RobolectricTestRunner::class)
internal class NetworkModuleTest {
@Test
fun `api client rejects a hostname that does not match the certificate`() {
val application = ApplicationProvider.getApplicationContext<Application>()
val interceptor = mockk<NetworkInterceptor>(relaxed = true)
val sslSession = Proxy.newProxyInstance(
NetworkModuleTest::class.java.classLoader,
arrayOf(SSLSession::class.java),
) { _, method, _ ->
if (method.name == "getPeerCertificates") {
throw SSLPeerUnverifiedException("no certificate for mismatched host")
}
null
} as SSLSession

val client = NetworkModule().provideOkHttpClient(application, interceptor)

assertFalse(client.hostnameVerifier().verify("attacker.invalid", sslSession))
}
}
Loading