From 4bb804fbf7664adf593147d2c5ddc34200417ab3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 30 Jun 2020 00:16:38 +0200 Subject: [PATCH 1/5] Allow self-signed certificate (#1564) Accepted fingerprint before the migration to RiotX should still work after the migration. The dialog to trust the certificate is displayed during the login flow. For the moment, it is not displayed if the certificate change on the server once the user is logged in. This use case will be handled later. --- CHANGES.md | 1 + .../matrix/android/api/failure/Failure.kt | 3 + .../matrix/android/api/failure/GlobalError.kt | 3 + .../auth/DefaultAuthenticationService.kt | 21 ++- .../internal/auth/login/DefaultLoginWizard.kt | 3 +- .../registration/DefaultRegistrationWizard.kt | 3 +- .../android/internal/network/Request.kt | 12 +- .../internal/network/RetrofitFactory.kt | 14 +- .../network/httpclient/OkHttpClientUtil.kt | 40 ++-- .../android/internal/network/ssl/CertUtil.kt | 28 ++- .../internal/network/ssl/Fingerprint.kt | 6 +- .../network/ssl/PinnedTrustManager.kt | 11 +- .../android/internal/session/SessionModule.kt | 16 +- .../session/identity/IdentityModule.kt | 11 +- .../vector/riotx/core/di/ScreenComponent.kt | 2 + .../vector/riotx/core/di/VectorComponent.kt | 3 + .../dialogs/UnrecognizedCertificateDialog.kt | 171 ++++++++++++++++++ .../vector/riotx/core/error/ErrorFormatter.kt | 12 +- .../riotx/core/platform/VectorBaseActivity.kt | 28 ++- .../riotx/core/platform/VectorBaseFragment.kt | 3 + .../features/login/AbstractLoginFragment.kt | 30 ++- .../riotx/features/login/LoginAction.kt | 3 + .../riotx/features/login/LoginActivity.kt | 11 +- .../riotx/features/login/LoginViewModel.kt | 121 +++++++------ .../res/layout/dialog_ssl_fingerprint.xml | 59 ++++++ vector/src/main/res/values/strings.xml | 2 + 26 files changed, 501 insertions(+), 116 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/dialogs/UnrecognizedCertificateDialog.kt create mode 100644 vector/src/main/res/layout/dialog_ssl_fingerprint.xml diff --git a/CHANGES.md b/CHANGES.md index 06c1424404..831ef4f081 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ Improvements 🙌: - Prioritising Recovery key over Recovery passphrase (#1463) - Room Settings: Name, Topic, Photo, Aliases, History Visibility (#1455) - Update user avatar (#1054) + - Allow self-signed certificate (#1564) Bugfix 🐛: - Fix dark theme issue on login screen (#1097) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt index 4d44e3346b..f519819d0d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.api.failure import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse +import im.vector.matrix.android.internal.network.ssl.Fingerprint import java.io.IOException /** @@ -32,9 +33,11 @@ import java.io.IOException sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) { data class Unknown(val throwable: Throwable? = null) : Failure(throwable) data class Cancelled(val throwable: Throwable? = null) : Failure(throwable) + data class UnrecognizedCertificateFailure(val url: String, val fingerprint: Fingerprint) : Failure() data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException) data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString())) object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false"))) + // When server send an error, but it cannot be interpreted as a MatrixError data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException("HTTP $httpCode: $errorBody")) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/GlobalError.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/GlobalError.kt index b2bc585258..ae268b9da2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/GlobalError.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/GlobalError.kt @@ -16,8 +16,11 @@ package im.vector.matrix.android.api.failure +import im.vector.matrix.android.internal.network.ssl.Fingerprint + // This class will be sent to the bus sealed class GlobalError { data class InvalidToken(val softLogout: Boolean) : GlobalError() data class ConsentNotGivenError(val consentUri: String) : GlobalError() + data class CertificateError(val fingerprint: Fingerprint) : GlobalError() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt index 2453bc0d05..473e77b95a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt @@ -43,6 +43,8 @@ import im.vector.matrix.android.internal.auth.version.isSupportedBySdk import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.network.httpclient.addSocketFactory +import im.vector.matrix.android.internal.network.ssl.UnrecognizedCertificateException import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.launchToCallback @@ -121,7 +123,11 @@ internal class DefaultAuthenticationService @Inject constructor( callback.onSuccess(it) }, { - callback.onFailure(it) + if (it is UnrecognizedCertificateException) { + callback.onFailure(Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint)) + } else { + callback.onFailure(it) + } } ) } @@ -248,7 +254,7 @@ internal class DefaultAuthenticationService @Inject constructor( ?: let { pendingSessionData?.homeServerConnectionConfig?.let { DefaultRegistrationWizard( - okHttpClient, + buildClient(it), retrofitFactory, coroutineDispatchers, sessionCreator, @@ -269,7 +275,7 @@ internal class DefaultAuthenticationService @Inject constructor( ?: let { pendingSessionData?.homeServerConnectionConfig?.let { DefaultLoginWizard( - okHttpClient, + buildClient(it), retrofitFactory, coroutineDispatchers, sessionCreator, @@ -347,7 +353,14 @@ internal class DefaultAuthenticationService @Inject constructor( } private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { - val retrofit = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString()) + val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUri.toString()) return retrofit.create(AuthAPI::class.java) } + + private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient { + return okHttpClient.get() + .newBuilder() + .addSocketFactory(homeServerConnectionConfig) + .build() + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt index 2ce9372903..522f571cf7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.auth.login import android.util.Patterns -import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.login.LoginWizard @@ -44,7 +43,7 @@ import kotlinx.coroutines.withContext import okhttp3.OkHttpClient internal class DefaultLoginWizard( - okHttpClient: Lazy, + okHttpClient: OkHttpClient, retrofitFactory: RetrofitFactory, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val sessionCreator: SessionCreator, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationWizard.kt index 750d806b6f..dafb024b7e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationWizard.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.auth.registration -import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.LoginFlowTypes import im.vector.matrix.android.api.auth.registration.RegisterThreePid @@ -41,7 +40,7 @@ import okhttp3.OkHttpClient * This class execute the registration request and is responsible to keep the session of interactive authentication */ internal class DefaultRegistrationWizard( - private val okHttpClient: Lazy, + private val okHttpClient: OkHttpClient, private val retrofitFactory: RetrofitFactory, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val sessionCreator: SessionCreator, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt index 44096fca71..5aaad6f7c8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.network import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.shouldBeRetried +import im.vector.matrix.android.internal.network.ssl.CertUtil import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import org.greenrobot.eventbus.EventBus @@ -26,7 +27,7 @@ import retrofit2.awaitResponse import java.io.IOException internal suspend inline fun executeRequest(eventBus: EventBus?, - block: Request.() -> Unit) = Request(eventBus).apply(block).execute() + block: Request.() -> Unit) = Request(eventBus).apply(block).execute() internal class Request(private val eventBus: EventBus?) { @@ -48,6 +49,15 @@ internal class Request(private val eventBus: EventBus?) { throw response.toFailure(eventBus) } } catch (exception: Throwable) { + // Check if this is a certificateException + CertUtil.getCertificateException(exception) + // TODO Support certificate error once logged + //?.also { unrecognizedCertificateException -> + // // Send the error to the bus, for a global management + // eventBus?.post(GlobalError.CertificateError(unrecognizedCertificateException)) + //} + ?.also { unrecognizedCertificateException -> throw unrecognizedCertificateException } + if (isRetryable && currentRetryCount++ < maxRetryCount && exception.shouldBeRetried()) { delay(currentDelay) currentDelay = (currentDelay * 2L).coerceAtMost(maxDelay) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitFactory.kt index ad26171793..0b087d7a1a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitFactory.kt @@ -26,7 +26,19 @@ import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import javax.inject.Inject -class RetrofitFactory @Inject constructor(private val moshi: Moshi) { +internal class RetrofitFactory @Inject constructor(private val moshi: Moshi) { + + /** + * Use only for authentication service + */ + fun create(okHttpClient: OkHttpClient, baseUrl: String): Retrofit { + return Retrofit.Builder() + .baseUrl(baseUrl.ensureTrailingSlash()) + .client(okHttpClient) + .addConverterFactory(UnitConverterFactory) + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .build() + } fun create(okHttpClient: Lazy, baseUrl: String): Retrofit { return Retrofit.Builder() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/httpclient/OkHttpClientUtil.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/httpclient/OkHttpClientUtil.kt index 8ffa0553e9..605d6b4a8c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/httpclient/OkHttpClientUtil.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/httpclient/OkHttpClientUtil.kt @@ -16,24 +16,38 @@ package im.vector.matrix.android.internal.network.httpclient +import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.internal.network.AccessTokenInterceptor import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor +import im.vector.matrix.android.internal.network.ssl.CertUtil import im.vector.matrix.android.internal.network.token.AccessTokenProvider import okhttp3.OkHttpClient +import timber.log.Timber -internal fun OkHttpClient.addAccessTokenInterceptor(accessTokenProvider: AccessTokenProvider): OkHttpClient { - return newBuilder() - .apply { - // Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor - val existingCurlInterceptors = interceptors().filterIsInstance() - interceptors().removeAll(existingCurlInterceptors) +internal fun OkHttpClient.Builder.addAccessTokenInterceptor(accessTokenProvider: AccessTokenProvider): OkHttpClient.Builder { + // Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor + val existingCurlInterceptors = interceptors().filterIsInstance() + interceptors().removeAll(existingCurlInterceptors) - addInterceptor(AccessTokenInterceptor(accessTokenProvider)) + addInterceptor(AccessTokenInterceptor(accessTokenProvider)) - // Re add eventually the curl logging interceptors - existingCurlInterceptors.forEach { - addInterceptor(it) - } - } - .build() + // Re add eventually the curl logging interceptors + existingCurlInterceptors.forEach { + addInterceptor(it) + } + + return this +} + +internal fun OkHttpClient.Builder.addSocketFactory(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient.Builder { + try { + val pair = CertUtil.newPinnedSSLSocketFactory(homeServerConnectionConfig) + sslSocketFactory(pair.sslSocketFactory, pair.x509TrustManager) + hostnameVerifier(CertUtil.newHostnameVerifier(homeServerConnectionConfig)) + connectionSpecs(CertUtil.newConnectionSpecs(homeServerConnectionConfig)) + } catch (e: Exception) { + Timber.e(e, "addSocketFactory failed") + } + + return this } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/CertUtil.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/CertUtil.kt index b304791b1c..2346ff8877 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/CertUtil.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/CertUtil.kt @@ -16,29 +16,30 @@ package im.vector.matrix.android.internal.network.ssl -import android.util.Pair import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import okhttp3.ConnectionSpec +import okhttp3.internal.tls.OkHostnameVerifier import timber.log.Timber import java.security.KeyStore import java.security.MessageDigest import java.security.cert.CertificateException import java.security.cert.X509Certificate import javax.net.ssl.HostnameVerifier -import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext import javax.net.ssl.SSLPeerUnverifiedException import javax.net.ssl.SSLSocketFactory import javax.net.ssl.TrustManager import javax.net.ssl.TrustManagerFactory import javax.net.ssl.X509TrustManager -import kotlin.experimental.and /** * Various utility classes for dealing with X509Certificates */ internal object CertUtil { + // Set to false to do some test + private const val USE_DEFAULT_HOSTNAME_VERIFIER = true + private val hexArray = "0123456789ABCDEF".toCharArray() /** @@ -95,11 +96,10 @@ internal object CertUtil { * @param fingerprint the fingerprint * @return the hexa string. */ - @JvmOverloads fun fingerprintToHexString(fingerprint: ByteArray, sep: Char = ' '): String { val hexChars = CharArray(fingerprint.size * 3) for (j in fingerprint.indices) { - val v = (fingerprint[j] and 0xFF.toByte()).toInt() + val v = (fingerprint[j].toInt() and 0xFF) hexChars[j * 3] = hexArray[v.ushr(4)] hexChars[j * 3 + 1] = hexArray[v and 0x0F] hexChars[j * 3 + 2] = sep @@ -128,13 +128,18 @@ internal object CertUtil { return null } + internal data class PinnedSSLSocketFactory( + val sslSocketFactory: SSLSocketFactory, + val x509TrustManager: X509TrustManager + ) + /** * Create a SSLSocket factory for a HS config. * * @param hsConfig the HS config. * @return SSLSocket factory */ - fun newPinnedSSLSocketFactory(hsConfig: HomeServerConnectionConfig): Pair { + fun newPinnedSSLSocketFactory(hsConfig: HomeServerConnectionConfig): PinnedSSLSocketFactory { try { var defaultTrustManager: X509TrustManager? = null @@ -155,7 +160,7 @@ internal object CertUtil { try { tf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) } catch (e: Exception) { - Timber.e(e, "## addRule : onBingRuleUpdateFailure failed") + Timber.e(e, "## newPinnedSSLSocketFactory() : TrustManagerFactory.getInstance of default failed") } } @@ -183,7 +188,7 @@ internal object CertUtil { sslSocketFactory = sslContext.socketFactory } - return Pair(sslSocketFactory, defaultTrustManager) + return PinnedSSLSocketFactory(sslSocketFactory, defaultTrustManager!!) } catch (e: Exception) { throw RuntimeException(e) } @@ -196,11 +201,14 @@ internal object CertUtil { * @return a new HostnameVerifier. */ fun newHostnameVerifier(hsConfig: HomeServerConnectionConfig): HostnameVerifier { - val defaultVerifier = HttpsURLConnection.getDefaultHostnameVerifier() + val defaultVerifier: HostnameVerifier = OkHostnameVerifier // HttpsURLConnection.getDefaultHostnameVerifier() val trustedFingerprints = hsConfig.allowedFingerprints return HostnameVerifier { hostname, session -> - if (defaultVerifier.verify(hostname, session)) return@HostnameVerifier true + if (USE_DEFAULT_HOSTNAME_VERIFIER) { + if (defaultVerifier.verify(hostname, session)) return@HostnameVerifier true + } + // TODO How to recover from this error? if (trustedFingerprints.isEmpty()) return@HostnameVerifier false // If remote cert matches an allowed fingerprint, just accept it. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/Fingerprint.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/Fingerprint.kt index dd8e70d459..e6139f3f9d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/Fingerprint.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/Fingerprint.kt @@ -32,7 +32,7 @@ data class Fingerprint( } @Throws(CertificateException::class) - fun matchesCert(cert: X509Certificate): Boolean { + internal fun matchesCert(cert: X509Certificate): Boolean { val o: Fingerprint? = when (hashType) { HashType.SHA256 -> newSha256Fingerprint(cert) HashType.SHA1 -> newSha1Fingerprint(cert) @@ -57,7 +57,7 @@ data class Fingerprint( return result } - companion object { + internal companion object { @Throws(CertificateException::class) fun newSha256Fingerprint(cert: X509Certificate): Fingerprint { @@ -79,6 +79,6 @@ data class Fingerprint( @JsonClass(generateAdapter = false) enum class HashType { @Json(name = "sha-1") SHA1, - @Json(name = "sha-256")SHA256 + @Json(name = "sha-256") SHA256 } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/PinnedTrustManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/PinnedTrustManager.kt index f3f9ecc562..f1c3652d0c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/PinnedTrustManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/PinnedTrustManager.kt @@ -34,16 +34,19 @@ import javax.net.ssl.X509TrustManager internal class PinnedTrustManager(private val fingerprints: List?, private val defaultTrustManager: X509TrustManager?) : X509TrustManager { + // Set to false to perform some test + private val USE_DEAFULT_TRUST_MANAGER = true + @Throws(CertificateException::class) override fun checkClientTrusted(chain: Array, s: String) { try { - if (defaultTrustManager != null) { + if (defaultTrustManager != null && USE_DEAFULT_TRUST_MANAGER) { defaultTrustManager.checkClientTrusted(chain, s) return } } catch (e: CertificateException) { // If there is an exception we fall back to checking fingerprints - if (fingerprints == null || fingerprints.isEmpty()) { + if (fingerprints.isNullOrEmpty()) { throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause) } } @@ -54,14 +57,14 @@ internal class PinnedTrustManager(private val fingerprints: List?, @Throws(CertificateException::class) override fun checkServerTrusted(chain: Array, s: String) { try { - if (defaultTrustManager != null) { + if (defaultTrustManager != null && USE_DEAFULT_TRUST_MANAGER) { defaultTrustManager.checkServerTrusted(chain, s) return } } catch (e: CertificateException) { // If there is an exception we fall back to checking fingerprints if (fingerprints == null || fingerprints.isEmpty()) { - throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause) + throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 2e9f6174f2..23280edbdb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -51,14 +51,14 @@ import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserMd5 import im.vector.matrix.android.internal.eventbus.EventBusTimberLogger -import im.vector.matrix.android.internal.network.AccessTokenInterceptor import im.vector.matrix.android.internal.network.DefaultNetworkConnectivityChecker import im.vector.matrix.android.internal.network.FallbackNetworkCallbackStrategy import im.vector.matrix.android.internal.network.NetworkCallbackStrategy import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.network.PreferredNetworkCallbackStrategy import im.vector.matrix.android.internal.network.RetrofitFactory -import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor +import im.vector.matrix.android.internal.network.httpclient.addAccessTokenInterceptor +import im.vector.matrix.android.internal.network.httpclient.addSocketFactory import im.vector.matrix.android.internal.network.token.AccessTokenProvider import im.vector.matrix.android.internal.network.token.HomeserverAccessTokenProvider import im.vector.matrix.android.internal.session.call.CallEventObserver @@ -189,23 +189,17 @@ internal abstract class SessionModule { @Authenticated fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient, @Authenticated accessTokenProvider: AccessTokenProvider, + homeServerConnectionConfig: HomeServerConnectionConfig, @SessionId sessionId: String, @MockHttpInterceptor testInterceptor: TestInterceptor?): OkHttpClient { return okHttpClient.newBuilder() + .addAccessTokenInterceptor(accessTokenProvider) + .addSocketFactory(homeServerConnectionConfig) .apply { - // Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor - val existingCurlInterceptors = interceptors().filterIsInstance() - interceptors().removeAll(existingCurlInterceptors) - - addInterceptor(AccessTokenInterceptor(accessTokenProvider)) if (testInterceptor != null) { testInterceptor.sessionId = sessionId addInterceptor(testInterceptor) } - // Re add eventually the curl logging interceptors - existingCurlInterceptors.forEach { - addInterceptor(it) - } } .build() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt index 7a5790788b..e906cf1938 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.identity import dagger.Binds import dagger.Module import dagger.Provides +import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.internal.database.RealmKeysUtils import im.vector.matrix.android.internal.di.AuthenticatedIdentity import im.vector.matrix.android.internal.di.IdentityDatabase @@ -26,6 +27,7 @@ import im.vector.matrix.android.internal.di.SessionFilesDirectory import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.di.UserMd5 import im.vector.matrix.android.internal.network.httpclient.addAccessTokenInterceptor +import im.vector.matrix.android.internal.network.httpclient.addSocketFactory import im.vector.matrix.android.internal.network.token.AccessTokenProvider import im.vector.matrix.android.internal.session.SessionModule import im.vector.matrix.android.internal.session.SessionScope @@ -46,8 +48,13 @@ internal abstract class IdentityModule { @SessionScope @AuthenticatedIdentity fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient, - @AuthenticatedIdentity accessTokenProvider: AccessTokenProvider): OkHttpClient { - return okHttpClient.addAccessTokenInterceptor(accessTokenProvider) + @AuthenticatedIdentity accessTokenProvider: AccessTokenProvider, + homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient { + return okHttpClient + .newBuilder() + .addAccessTokenInterceptor(accessTokenProvider) + .addSocketFactory(homeServerConnectionConfig) + .build() } @JvmStatic diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt index 7bba3cb5d4..ceb276614a 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt @@ -21,6 +21,7 @@ import androidx.fragment.app.FragmentFactory import androidx.lifecycle.ViewModelProvider import dagger.BindsInstance import dagger.Component +import im.vector.riotx.core.dialogs.UnrecognizedCertificateDialog import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.preference.UserAvatarPreference import im.vector.riotx.features.MainActivity @@ -100,6 +101,7 @@ interface ScreenComponent { fun navigator(): Navigator fun errorFormatter(): ErrorFormatter fun uiStateRepository(): UiStateRepository + fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog /* ========================================================================================== * Activities diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt index 14f3019666..36dda5aa39 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt @@ -27,6 +27,7 @@ import im.vector.riotx.ActiveSessionDataSource import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.EmojiCompatWrapper import im.vector.riotx.VectorApplication +import im.vector.riotx.core.dialogs.UnrecognizedCertificateDialog import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.pushers.PushersManager import im.vector.riotx.core.utils.AssetReader @@ -89,6 +90,8 @@ interface VectorComponent { fun activeSessionHolder(): ActiveSessionHolder + fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog + fun emojiCompatFontProvider(): EmojiCompatFontProvider fun emojiCompatWrapper(): EmojiCompatWrapper diff --git a/vector/src/main/java/im/vector/riotx/core/dialogs/UnrecognizedCertificateDialog.kt b/vector/src/main/java/im/vector/riotx/core/dialogs/UnrecognizedCertificateDialog.kt new file mode 100644 index 0000000000..0c7ed6e714 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/dialogs/UnrecognizedCertificateDialog.kt @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.riotx.core.dialogs + +import android.app.Activity +import android.view.View +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import im.vector.matrix.android.internal.network.ssl.Fingerprint +import im.vector.riotx.R +import im.vector.riotx.core.di.ActiveSessionHolder +import im.vector.riotx.core.resources.StringProvider +import timber.log.Timber +import java.util.HashMap +import java.util.HashSet +import javax.inject.Inject + +/** + * This class displays the unknown certificate dialog + */ +class UnrecognizedCertificateDialog @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, + private val stringProvider: StringProvider +) { + private val ignoredFingerprints: MutableMap> = HashMap() + private val openDialogIds: MutableSet = HashSet() + + /** + * Display a certificate dialog box, asking the user about an unknown certificate + * To use when user is currently logged in + * + * @param unrecognizedFingerprint the fingerprint for the unknown certificate + * @param callback callback to fire when the user makes a decision + */ + fun show(activity: Activity, + unrecognizedFingerprint: Fingerprint, + callback: Callback) { + val userId = activeSessionHolder.getSafeActiveSession()?.myUserId + val hsConfig = activeSessionHolder.getSafeActiveSession()?.sessionParams?.homeServerConnectionConfig ?: return + + internalShow(activity, unrecognizedFingerprint, true, callback, userId, hsConfig.homeServerUri.toString(), hsConfig.allowedFingerprints.isNotEmpty()) + } + + /** + * To use during login flow + */ + fun show(activity: Activity, + unrecognizedFingerprint: Fingerprint, + homeServerUrl: String, + callback: Callback) { + internalShow(activity, unrecognizedFingerprint, false, callback, null, homeServerUrl, false) + } + + /** + * Display a certificate dialog box, asking the user about an unknown certificate + * + * @param unrecognizedFingerprint the fingerprint for the unknown certificate + * @param existing the current session already exist, so it mean that something has changed server side + * @param callback callback to fire when the user makes a decision + */ + private fun internalShow(activity: Activity, + unrecognizedFingerprint: Fingerprint, + existing: Boolean, + callback: Callback, + userId: String?, + homeServerUrl: String, + homeServerConnectionConfigHasFingerprints: Boolean) { + val dialogId = userId ?: homeServerUrl + unrecognizedFingerprint.displayableHexRepr + + if (openDialogIds.contains(dialogId)) { + Timber.i("Not opening dialog $dialogId as one is already open.") + return + } + + if (userId != null) { + val f: Set? = ignoredFingerprints[userId] + if (f != null && f.contains(unrecognizedFingerprint)) { + callback.onIgnore() + return + } + } + + val builder = AlertDialog.Builder(activity) + val inflater = activity.layoutInflater + val layout: View = inflater.inflate(R.layout.dialog_ssl_fingerprint, null) + val sslFingerprintTitle = layout.findViewById(R.id.ssl_fingerprint_title) + sslFingerprintTitle.text = stringProvider.getString(R.string.ssl_fingerprint_hash, unrecognizedFingerprint.hashType.toString()) + val sslFingerprint = layout.findViewById(R.id.ssl_fingerprint) + sslFingerprint.text = unrecognizedFingerprint.displayableHexRepr + val sslUserId = layout.findViewById(R.id.ssl_user_id) + if (userId != null) { + sslUserId.text = stringProvider.getString(R.string.generic_label_and_value, + stringProvider.getString(R.string.username), + userId) + } else { + sslUserId.text = stringProvider.getString(R.string.generic_label_and_value, + stringProvider.getString(R.string.hs_url), + homeServerUrl) + } + val sslExpl = layout.findViewById(R.id.ssl_explanation) + if (existing) { + if (homeServerConnectionConfigHasFingerprints) { + sslExpl.text = stringProvider.getString(R.string.ssl_expected_existing_expl) + } else { + sslExpl.text = stringProvider.getString(R.string.ssl_unexpected_existing_expl) + } + } else { + sslExpl.text = stringProvider.getString(R.string.ssl_cert_new_account_expl) + } + builder.setView(layout) + builder.setTitle(R.string.ssl_could_not_verify) + builder.setPositiveButton(R.string.ssl_trust) { _, _ -> + callback.onAccept() + } + if (existing) { + builder.setNegativeButton(R.string.ssl_remain_offline) { _, _ -> + if (userId != null) { + var f = ignoredFingerprints[userId] + if (f == null) { + f = HashSet() + ignoredFingerprints[userId] = f + } + f.add(unrecognizedFingerprint) + } + callback.onIgnore() + } + builder.setNeutralButton(R.string.ssl_logout_account) { _, _ -> callback.onReject() } + } else { + builder.setNegativeButton(R.string.cancel) { _, _ -> callback.onReject() } + } + + builder.setOnDismissListener { + Timber.d("Dismissed!") + openDialogIds.remove(dialogId) + + } + + builder.show() + openDialogIds.add(dialogId) + } + + interface Callback { + /** + * The certificate was explicitly accepted + */ + fun onAccept() + + /** + * The warning was ignored by the user + */ + fun onIgnore() + + /** + * The unknown certificate was explicitly rejected + */ + fun onReject() + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt index b2e02d564c..907107c90b 100644 --- a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt @@ -26,6 +26,8 @@ import java.net.HttpURLConnection import java.net.SocketTimeoutException import java.net.UnknownHostException import javax.inject.Inject +import javax.net.ssl.SSLException +import javax.net.ssl.SSLPeerUnverifiedException interface ErrorFormatter { fun toHumanReadable(throwable: Throwable?): String @@ -41,13 +43,17 @@ class DefaultErrorFormatter @Inject constructor( is IdentityServiceError -> identityServerError(throwable) is Failure.NetworkConnection -> { when (throwable.ioException) { - is SocketTimeoutException -> + is SocketTimeoutException -> stringProvider.getString(R.string.error_network_timeout) - is UnknownHostException -> + is UnknownHostException -> // Invalid homeserver? // TODO Check network state, airplane mode, etc. stringProvider.getString(R.string.login_error_unknown_host) - else -> + is SSLPeerUnverifiedException -> + stringProvider.getString(R.string.login_error_ssl_peer_unverified) + is SSLException -> + stringProvider.getString(R.string.login_error_ssl_other) + else -> stringProvider.getString(R.string.error_no_network) } } diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt index ba9e7320d2..bdd873d0cd 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt @@ -55,7 +55,10 @@ import im.vector.riotx.core.di.HasVectorInjector import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.VectorComponent import im.vector.riotx.core.dialogs.DialogLocker +import im.vector.riotx.core.dialogs.UnrecognizedCertificateDialog +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.observeEvent +import im.vector.riotx.core.extensions.vectorComponent import im.vector.riotx.core.utils.toast import im.vector.riotx.features.MainActivity import im.vector.riotx.features.MainActivityArgs @@ -231,7 +234,30 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector { is GlobalError.ConsentNotGivenError -> consentNotGivenHelper.displayDialog(globalError.consentUri, activeSessionHolder.getActiveSession().sessionParams.homeServerHost ?: "") - } + is GlobalError.CertificateError -> + handleCertificateError(globalError) + }.exhaustive + } + + private fun handleCertificateError(certificateError: GlobalError.CertificateError) { + vectorComponent() + .unrecognizedCertificateDialog() + .show(this, + certificateError.fingerprint, + object : UnrecognizedCertificateDialog.Callback { + override fun onAccept() { + // TODO Support certificate error once logged + } + + override fun onIgnore() { + // TODO Support certificate error once logged + } + + override fun onReject() { + // TODO Support certificate error once logged + } + } + ) } protected open fun handleInvalidToken(globalError: GlobalError.InvalidToken) { diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt index c4dcb0d996..c0b1b54c09 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt @@ -44,6 +44,7 @@ import im.vector.riotx.R import im.vector.riotx.core.di.DaggerScreenComponent import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.di.ScreenComponent +import im.vector.riotx.core.dialogs.UnrecognizedCertificateDialog import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.features.navigation.Navigator import io.reactivex.android.schedulers.AndroidSchedulers @@ -69,6 +70,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { protected lateinit var navigator: Navigator protected lateinit var errorFormatter: ErrorFormatter + protected lateinit var unrecognizedCertificateDialog: UnrecognizedCertificateDialog private var progress: ProgressDialog? = null @@ -92,6 +94,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { screenComponent = DaggerScreenComponent.factory().create(vectorBaseActivity.getVectorComponent(), vectorBaseActivity) navigator = screenComponent.navigator() errorFormatter = screenComponent.errorFormatter() + unrecognizedCertificateDialog = screenComponent.unrecognizedCertificateDialog() viewModelFactory = screenComponent.viewModelFactory() childFragmentManager.fragmentFactory = screenComponent.fragmentFactory() injectWith(injector()) diff --git a/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt index 8fceaad07f..5927e5b117 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt @@ -27,6 +27,7 @@ import com.airbnb.mvrx.withState import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError import im.vector.riotx.R +import im.vector.riotx.core.dialogs.UnrecognizedCertificateDialog import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.OnBackPressed import im.vector.riotx.core.platform.VectorBaseFragment @@ -72,7 +73,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed { override fun showFailure(throwable: Throwable) { when (throwable) { - is Failure.ServerError -> { + is Failure.ServerError -> if (throwable.error.code == MatrixError.M_FORBIDDEN && throwable.httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */) { AlertDialog.Builder(requireActivity()) @@ -83,11 +84,34 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed { } else { onError(throwable) } - } - else -> onError(throwable) + is Failure.UnrecognizedCertificateFailure -> + showUnrecognizedCertificateFailure(throwable) + else -> + onError(throwable) } } + private fun showUnrecognizedCertificateFailure(failure: Failure.UnrecognizedCertificateFailure) { + // Ask the user to accept the certificate + unrecognizedCertificateDialog.show(requireActivity(), + failure.fingerprint, + failure.url, + object : UnrecognizedCertificateDialog.Callback { + override fun onAccept() { + // User accept the certificate + loginViewModel.handle(LoginAction.UserAcceptCertificate(failure.fingerprint)) + } + + override fun onIgnore() { + // Cannot happen in this case + } + + override fun onReject() { + // Nothing to do in this case + } + }) + } + open fun onError(throwable: Throwable) { super.showFailure(throwable) } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt index afd27f04a7..d083f6e9f7 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.login import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.registration.RegisterThreePid +import im.vector.matrix.android.internal.network.ssl.Fingerprint import im.vector.riotx.core.platform.VectorViewModelAction sealed class LoginAction : VectorViewModelAction { @@ -61,4 +62,6 @@ sealed class LoginAction : VectorViewModelAction { data class SetupSsoForSessionRecovery(val homeServerUrl: String, val deviceId: String) : LoginAction() data class PostViewEvent(val viewEvent: LoginViewEvents) : LoginAction() + + data class UserAcceptCertificate(val fingerprint: Fingerprint) : LoginAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt index 13bde075a6..86be00702c 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt @@ -133,15 +133,14 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { } } } - is LoginViewEvents.OutdatedHomeserver -> + is LoginViewEvents.OutdatedHomeserver -> { AlertDialog.Builder(this) .setTitle(R.string.login_error_outdated_homeserver_title) .setMessage(R.string.login_error_outdated_homeserver_content) .setPositiveButton(R.string.ok, null) .show() - is LoginViewEvents.Failure -> - // This is handled by the Fragments Unit + } is LoginViewEvents.OpenServerSelection -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerSelectionFragment::class.java, @@ -195,7 +194,11 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginViewEvents.msisdn), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) - } + is LoginViewEvents.Failure, + is LoginViewEvents.Loading -> + // This is handled by the Fragments + Unit + }.exhaustive } private fun updateWithState(loginViewState: LoginViewState) { diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index 98cff4f6d9..1036a1fd0a 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -87,6 +87,8 @@ class LoginViewModel @AssistedInject constructor( } } + private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null + val currentThreePid: String? get() = registrationWizard?.currentThreePid @@ -118,10 +120,18 @@ class LoginViewModel @AssistedInject constructor( is LoginAction.RegisterAction -> handleRegisterAction(action) is LoginAction.ResetAction -> handleResetAction(action) is LoginAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action) + is LoginAction.UserAcceptCertificate -> handleUserAcceptCertificate(action) is LoginAction.PostViewEvent -> _viewEvents.post(action.viewEvent) }.exhaustive } + private fun handleUserAcceptCertificate(action: LoginAction.UserAcceptCertificate) { + // It happen when we get the login flow, so alter the homeserver config and retrieve again the login flow + currentHomeServerConnectionConfig + ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } + ?.let { getLoginFlow(it) } + } + private fun handleLoginWithToken(action: LoginAction.LoginWithToken) { val safeLoginWizard = loginWizard @@ -649,67 +659,74 @@ class LoginViewModel @AssistedInject constructor( // This is invalid _viewEvents.post(LoginViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig"))) } else { - currentTask?.cancel() - currentTask = null - authenticationService.cancelPendingLoginOrRegistration() + getLoginFlow(homeServerConnectionConfig) + } + } - setState { - copy( - asyncHomeServerLoginFlowRequest = Loading() - ) + private fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig) { + currentHomeServerConnectionConfig = homeServerConnectionConfig + + currentTask?.cancel() + currentTask = null + authenticationService.cancelPendingLoginOrRegistration() + + setState { + copy( + asyncHomeServerLoginFlowRequest = Loading() + ) + } + + currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback { + override fun onFailure(failure: Throwable) { + _viewEvents.post(LoginViewEvents.Failure(failure)) + setState { + copy( + asyncHomeServerLoginFlowRequest = Uninitialized + ) + } } - currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback { - override fun onFailure(failure: Throwable) { - _viewEvents.post(LoginViewEvents.Failure(failure)) - setState { - copy( - asyncHomeServerLoginFlowRequest = Uninitialized - ) - } - } - - override fun onSuccess(data: LoginFlowResult) { - when (data) { - is LoginFlowResult.Success -> { - val loginMode = when { - // SSO login is taken first - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password - else -> LoginMode.Unsupported - } - - if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) { - notSupported() - } else { - setState { - copy( - asyncHomeServerLoginFlowRequest = Uninitialized, - homeServerUrl = data.homeServerUrl, - loginMode = loginMode, - loginModeSupportedTypes = data.supportedLoginTypes.toList() - ) - } - } + override fun onSuccess(data: LoginFlowResult) { + when (data) { + is LoginFlowResult.Success -> { + val loginMode = when { + // SSO login is taken first + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password + else -> LoginMode.Unsupported } - is LoginFlowResult.OutdatedHomeserver -> { + + if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) { notSupported() + } else { + // FIXME We should post a view event here normally? + setState { + copy( + asyncHomeServerLoginFlowRequest = Uninitialized, + homeServerUrl = data.homeServerUrl, + loginMode = loginMode, + loginModeSupportedTypes = data.supportedLoginTypes.toList() + ) + } } } - } - - private fun notSupported() { - // Notify the UI - _viewEvents.post(LoginViewEvents.OutdatedHomeserver) - - setState { - copy( - asyncHomeServerLoginFlowRequest = Uninitialized - ) + is LoginFlowResult.OutdatedHomeserver -> { + notSupported() } } - }) - } + } + + private fun notSupported() { + // Notify the UI + _viewEvents.post(LoginViewEvents.OutdatedHomeserver) + + setState { + copy( + asyncHomeServerLoginFlowRequest = Uninitialized + ) + } + } + }) } override fun onCleared() { diff --git a/vector/src/main/res/layout/dialog_ssl_fingerprint.xml b/vector/src/main/res/layout/dialog_ssl_fingerprint.xml new file mode 100644 index 0000000000..3c1abc11a2 --- /dev/null +++ b/vector/src/main/res/layout/dialog_ssl_fingerprint.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 83e0d847ac..05aa7d27bb 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -298,6 +298,8 @@ This URL is not reachable, please check it This is not a valid Matrix server address Cannot reach a homeserver at this URL, please check it + "SSL Error: the peer's identity has not been verified." + "SSL Error." Your device is using an outdated TLS security protocol, vulnerable to attack, for your security you will not be able to connect Mobile From c14b226c92bef5ff233457c2e261ca61edce93af Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 30 Jun 2020 00:50:17 +0200 Subject: [PATCH 2/5] Search only Kotlin long files --- tools/check/check_code_quality.sh | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tools/check/check_code_quality.sh b/tools/check/check_code_quality.sh index 72c531bf39..8f850734fc 100755 --- a/tools/check/check_code_quality.sh +++ b/tools/check/check_code_quality.sh @@ -108,14 +108,22 @@ else chmod u+x ${checkLongFilesScript} fi -echo -echo "Search for long files..." +maxLines=2500 -${checkLongFilesScript} 2500 \ +echo +echo "Search for kotlin files with more than ${maxLines} lines..." + +${checkLongFilesScript} ${maxLines} \ + ./matrix-sdk-android/src/main/java \ + ./matrix-sdk-android-rx/src/main/java \ + ./vector/src/androidTest/java \ + ./vector/src/debug/java \ + ./vector/src/fdroid/java \ + ./vector/src/gplay/java \ ./vector/src/main/java \ - ./vector/src/main/res/layout \ - ./vector/src/main/res/values \ - ./vector/src/main/res/values-v21 \ + ./vector/src/release/java \ + ./vector/src/sharedTest/java \ + ./vector/src/test/java resultLongFiles=$? From 6721e33c7ee89f50882cf7f5527961454505e081 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 30 Jun 2020 00:51:11 +0200 Subject: [PATCH 3/5] cleanup --- .../java/im/vector/matrix/android/internal/network/Request.kt | 4 ++-- .../riotx/core/dialogs/UnrecognizedCertificateDialog.kt | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt index 5aaad6f7c8..2ba058d0db 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt @@ -52,10 +52,10 @@ internal class Request(private val eventBus: EventBus?) { // Check if this is a certificateException CertUtil.getCertificateException(exception) // TODO Support certificate error once logged - //?.also { unrecognizedCertificateException -> + // ?.also { unrecognizedCertificateException -> // // Send the error to the bus, for a global management // eventBus?.post(GlobalError.CertificateError(unrecognizedCertificateException)) - //} + // } ?.also { unrecognizedCertificateException -> throw unrecognizedCertificateException } if (isRetryable && currentRetryCount++ < maxRetryCount && exception.shouldBeRetried()) { diff --git a/vector/src/main/java/im/vector/riotx/core/dialogs/UnrecognizedCertificateDialog.kt b/vector/src/main/java/im/vector/riotx/core/dialogs/UnrecognizedCertificateDialog.kt index 0c7ed6e714..af2c23848f 100644 --- a/vector/src/main/java/im/vector/riotx/core/dialogs/UnrecognizedCertificateDialog.kt +++ b/vector/src/main/java/im/vector/riotx/core/dialogs/UnrecognizedCertificateDialog.kt @@ -145,7 +145,6 @@ class UnrecognizedCertificateDialog @Inject constructor( builder.setOnDismissListener { Timber.d("Dismissed!") openDialogIds.remove(dialogId) - } builder.show() @@ -168,4 +167,4 @@ class UnrecognizedCertificateDialog @Inject constructor( */ fun onReject() } -} \ No newline at end of file +} From ba26aee54cb10320c8cd45c5e31033ea467e6c9f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 30 Jun 2020 11:32:14 +0200 Subject: [PATCH 4/5] Use OkHttpClient with certificate to download files, and to perform wellknown request, and to get terms --- .../android/api/auth/AuthenticationService.kt | 1 + .../auth/DefaultAuthenticationService.kt | 8 +++-- .../internal/auth/login/DirectLoginTask.kt | 11 +++++- .../android/internal/di/AuthQualifiers.kt | 4 +++ .../network/ssl/PinnedTrustManager.kt | 6 ++-- .../internal/session/DefaultFileService.kt | 4 +-- .../android/internal/session/SessionModule.kt | 20 ++++++++--- .../DefaultGetHomeServerCapabilitiesTask.kt | 4 ++- .../identity/DefaultIdentityService.kt | 4 +-- .../session/identity/IdentityModule.kt | 10 ++---- .../session/terms/DefaultTermsService.kt | 4 +-- .../internal/session/terms/TermsModule.kt | 4 +-- .../internal/wellknown/GetWellknownTask.kt | 35 +++++++++++++------ .../riotx/features/login/LoginViewModel.kt | 3 +- 14 files changed, 80 insertions(+), 38 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/AuthenticationService.kt index effeae596a..08007e3397 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/AuthenticationService.kt @@ -89,6 +89,7 @@ interface AuthenticationService { * Perform a wellknown request, using the domain from the matrixId */ fun getWellKnownData(matrixId: String, + homeServerConnectionConfig: HomeServerConnectionConfig?, callback: MatrixCallback): Cancelable /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt index 473e77b95a..087e00848c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt @@ -215,7 +215,7 @@ internal class DefaultAuthenticationService @Inject constructor( // Create a fake userId, for the getWellknown task val fakeUserId = "@alice:$domain" - val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(fakeUserId)) + val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(fakeUserId, homeServerConnectionConfig)) return when (wellknownResult) { is WellknownResult.Prompt -> { @@ -327,9 +327,11 @@ internal class DefaultAuthenticationService @Inject constructor( } } - override fun getWellKnownData(matrixId: String, callback: MatrixCallback): Cancelable { + override fun getWellKnownData(matrixId: String, + homeServerConnectionConfig: HomeServerConnectionConfig?, + callback: MatrixCallback): Cancelable { return getWellknownTask - .configureWith(GetWellknownTask.Params(matrixId)) { + .configureWith(GetWellknownTask.Params(matrixId, homeServerConnectionConfig)) { this.callback = callback } .executeBy(taskExecutor) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DirectLoginTask.kt index 90eddf2e14..22a921a094 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DirectLoginTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DirectLoginTask.kt @@ -26,6 +26,7 @@ import im.vector.matrix.android.internal.auth.data.PasswordLoginParams import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.network.httpclient.addSocketFactory import im.vector.matrix.android.internal.task.Task import okhttp3.OkHttpClient import javax.inject.Inject @@ -47,7 +48,8 @@ internal class DefaultDirectLoginTask @Inject constructor( ) : DirectLoginTask { override suspend fun execute(params: DirectLoginTask.Params): Session { - val authAPI = retrofitFactory.create(okHttpClient, params.homeServerConnectionConfig.homeServerUri.toString()) + val client = buildClient(params.homeServerConnectionConfig) + val authAPI = retrofitFactory.create(client, params.homeServerConnectionConfig.homeServerUri.toString()) .create(AuthAPI::class.java) val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName) @@ -58,4 +60,11 @@ internal class DefaultDirectLoginTask @Inject constructor( return sessionCreator.createSession(credentials, params.homeServerConnectionConfig) } + + private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient { + return okHttpClient.get() + .newBuilder() + .addSocketFactory(homeServerConnectionConfig) + .build() + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/AuthQualifiers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/AuthQualifiers.kt index 0ceb94caa7..105d904329 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/AuthQualifiers.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/AuthQualifiers.kt @@ -29,3 +29,7 @@ internal annotation class AuthenticatedIdentity @Qualifier @Retention(AnnotationRetention.RUNTIME) internal annotation class Unauthenticated + +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +internal annotation class UnauthenticatedWithCertificate diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/PinnedTrustManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/PinnedTrustManager.kt index f1c3652d0c..de41d168fd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/PinnedTrustManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/ssl/PinnedTrustManager.kt @@ -35,12 +35,12 @@ internal class PinnedTrustManager(private val fingerprints: List?, private val defaultTrustManager: X509TrustManager?) : X509TrustManager { // Set to false to perform some test - private val USE_DEAFULT_TRUST_MANAGER = true + private val USE_DEFAULT_TRUST_MANAGER = true @Throws(CertificateException::class) override fun checkClientTrusted(chain: Array, s: String) { try { - if (defaultTrustManager != null && USE_DEAFULT_TRUST_MANAGER) { + if (defaultTrustManager != null && USE_DEFAULT_TRUST_MANAGER) { defaultTrustManager.checkClientTrusted(chain, s) return } @@ -57,7 +57,7 @@ internal class PinnedTrustManager(private val fingerprints: List?, @Throws(CertificateException::class) override fun checkServerTrusted(chain: Array, s: String) { try { - if (defaultTrustManager != null && USE_DEAFULT_TRUST_MANAGER) { + if (defaultTrustManager != null && USE_DEFAULT_TRUST_MANAGER) { defaultTrustManager.checkServerTrusted(chain, s) return } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt index 0cdd39f117..107ef6a351 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt @@ -26,7 +26,7 @@ import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachmen import im.vector.matrix.android.internal.di.CacheDirectory import im.vector.matrix.android.internal.di.ExternalFilesDirectory import im.vector.matrix.android.internal.di.SessionCacheDirectory -import im.vector.matrix.android.internal.di.Unauthenticated +import im.vector.matrix.android.internal.di.UnauthenticatedWithCertificate import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers @@ -49,7 +49,7 @@ internal class DefaultFileService @Inject constructor( @SessionCacheDirectory private val sessionCacheDirectory: File, private val contentUrlResolver: ContentUrlResolver, - @Unauthenticated + @UnauthenticatedWithCertificate private val okHttpClient: OkHttpClient, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val taskExecutor: TaskExecutor diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 23280edbdb..f084bec924 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -48,6 +48,7 @@ import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.di.SessionFilesDirectory import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.di.Unauthenticated +import im.vector.matrix.android.internal.di.UnauthenticatedWithCertificate import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserMd5 import im.vector.matrix.android.internal.eventbus.EventBusTimberLogger @@ -183,18 +184,29 @@ internal abstract class SessionModule { .build() } + @JvmStatic + @Provides + @SessionScope + @UnauthenticatedWithCertificate + fun providesOkHttpClientWithCertificate(@Unauthenticated okHttpClient: OkHttpClient, + homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient { + return okHttpClient + .newBuilder() + .addSocketFactory(homeServerConnectionConfig) + .build() + } + @JvmStatic @Provides @SessionScope @Authenticated - fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient, + fun providesOkHttpClient(@UnauthenticatedWithCertificate okHttpClient: OkHttpClient, @Authenticated accessTokenProvider: AccessTokenProvider, - homeServerConnectionConfig: HomeServerConnectionConfig, @SessionId sessionId: String, @MockHttpInterceptor testInterceptor: TestInterceptor?): OkHttpClient { - return okHttpClient.newBuilder() + return okHttpClient + .newBuilder() .addAccessTokenInterceptor(accessTokenProvider) - .addSocketFactory(homeServerConnectionConfig) .apply { if (testInterceptor != null) { testInterceptor.sessionId = sessionId diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt index 8b2f371317..b26bbe7c5c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.session.homeserver import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.wellknown.WellknownResult import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities import im.vector.matrix.android.internal.auth.version.Versions @@ -43,6 +44,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( private val eventBus: EventBus, private val getWellknownTask: GetWellknownTask, private val configExtractor: IntegrationManagerConfigExtractor, + private val homeServerConnectionConfig: HomeServerConnectionConfig, @UserId private val userId: String ) : GetHomeServerCapabilitiesTask { @@ -78,7 +80,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( }.getOrNull() val wellknownResult = runCatching { - getWellknownTask.execute(GetWellknownTask.Params(userId)) + getWellknownTask.execute(GetWellknownTask.Params(userId, homeServerConnectionConfig)) }.getOrNull() insertInDb(capabilities, uploadCapabilities, versions, wellknownResult) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt index 4afd045d0f..3f10bf791c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt @@ -36,7 +36,7 @@ import im.vector.matrix.android.api.session.identity.ThreePid import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.NoOpCancellable import im.vector.matrix.android.internal.di.AuthenticatedIdentity -import im.vector.matrix.android.internal.di.Unauthenticated +import im.vector.matrix.android.internal.di.UnauthenticatedWithCertificate import im.vector.matrix.android.internal.extensions.observeNotNull import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.session.SessionLifecycleObserver @@ -68,7 +68,7 @@ internal class DefaultIdentityService @Inject constructor( private val identityPingTask: IdentityPingTask, private val identityDisconnectTask: IdentityDisconnectTask, private val identityRequestTokenForBindingTask: IdentityRequestTokenForBindingTask, - @Unauthenticated + @UnauthenticatedWithCertificate private val unauthenticatedOkHttpClient: Lazy, @AuthenticatedIdentity private val okHttpClient: Lazy, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt index e906cf1938..9f902f79f1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt @@ -19,15 +19,13 @@ package im.vector.matrix.android.internal.session.identity import dagger.Binds import dagger.Module import dagger.Provides -import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.internal.database.RealmKeysUtils import im.vector.matrix.android.internal.di.AuthenticatedIdentity import im.vector.matrix.android.internal.di.IdentityDatabase import im.vector.matrix.android.internal.di.SessionFilesDirectory -import im.vector.matrix.android.internal.di.Unauthenticated +import im.vector.matrix.android.internal.di.UnauthenticatedWithCertificate import im.vector.matrix.android.internal.di.UserMd5 import im.vector.matrix.android.internal.network.httpclient.addAccessTokenInterceptor -import im.vector.matrix.android.internal.network.httpclient.addSocketFactory import im.vector.matrix.android.internal.network.token.AccessTokenProvider import im.vector.matrix.android.internal.session.SessionModule import im.vector.matrix.android.internal.session.SessionScope @@ -47,13 +45,11 @@ internal abstract class IdentityModule { @Provides @SessionScope @AuthenticatedIdentity - fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient, - @AuthenticatedIdentity accessTokenProvider: AccessTokenProvider, - homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient { + fun providesOkHttpClient(@UnauthenticatedWithCertificate okHttpClient: OkHttpClient, + @AuthenticatedIdentity accessTokenProvider: AccessTokenProvider): OkHttpClient { return okHttpClient .newBuilder() .addAccessTokenInterceptor(accessTokenProvider) - .addSocketFactory(homeServerConnectionConfig) .build() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/DefaultTermsService.kt index 9111c5d5f1..1781fcc3dc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/DefaultTermsService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/DefaultTermsService.kt @@ -22,7 +22,7 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.terms.GetTermsResponse import im.vector.matrix.android.api.session.terms.TermsService import im.vector.matrix.android.api.util.Cancelable -import im.vector.matrix.android.internal.di.Unauthenticated +import im.vector.matrix.android.internal.di.UnauthenticatedWithCertificate import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.executeRequest @@ -41,7 +41,7 @@ import okhttp3.OkHttpClient import javax.inject.Inject internal class DefaultTermsService @Inject constructor( - @Unauthenticated + @UnauthenticatedWithCertificate private val unauthenticatedOkHttpClient: Lazy, private val accountDataDataSource: AccountDataDataSource, private val termsAPI: TermsAPI, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsModule.kt index eee7e22134..a948101638 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsModule.kt @@ -21,7 +21,7 @@ import dagger.Lazy import dagger.Module import dagger.Provides import im.vector.matrix.android.api.session.terms.TermsService -import im.vector.matrix.android.internal.di.Unauthenticated +import im.vector.matrix.android.internal.di.UnauthenticatedWithCertificate import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.session.SessionScope import okhttp3.OkHttpClient @@ -34,7 +34,7 @@ internal abstract class TermsModule { @Provides @JvmStatic @SessionScope - fun providesTermsAPI(@Unauthenticated unauthenticatedOkHttpClient: Lazy, + fun providesTermsAPI(@UnauthenticatedWithCertificate unauthenticatedOkHttpClient: Lazy, retrofitFactory: RetrofitFactory): TermsAPI { val retrofit = retrofitFactory.create(unauthenticatedOkHttpClient, "https://foo.bar") return retrofit.create(TermsAPI::class.java) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/GetWellknownTask.kt index c6f6b8752d..8ded737c64 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/GetWellknownTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/GetWellknownTask.kt @@ -19,12 +19,14 @@ package im.vector.matrix.android.internal.wellknown import android.util.MalformedJsonException import dagger.Lazy import im.vector.matrix.android.api.MatrixPatterns +import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.WellKnown import im.vector.matrix.android.api.auth.wellknown.WellknownResult import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.network.httpclient.addSocketFactory import im.vector.matrix.android.internal.session.homeserver.CapabilitiesAPI import im.vector.matrix.android.internal.session.identity.IdentityAuthAPI import im.vector.matrix.android.internal.task.Task @@ -36,7 +38,8 @@ import javax.net.ssl.HttpsURLConnection internal interface GetWellknownTask : Task { data class Params( - val matrixId: String + val matrixId: String, + val homeServerConnectionConfig: HomeServerConnectionConfig? ) } @@ -56,7 +59,19 @@ internal class DefaultGetWellknownTask @Inject constructor( val homeServerDomain = params.matrixId.substringAfter(":") - return findClientConfig(homeServerDomain) + val client = buildClient(params.homeServerConnectionConfig) + return findClientConfig(homeServerDomain, client) + } + + private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig?): OkHttpClient { + return if (homeServerConnectionConfig != null) { + okHttpClient.get() + .newBuilder() + .addSocketFactory(homeServerConnectionConfig) + .build() + } else { + okHttpClient.get() + } } /** @@ -68,8 +83,8 @@ internal class DefaultGetWellknownTask @Inject constructor( * * @param domain: homeserver domain, deduced from mx userId (ex: "matrix.org" from userId "@user:matrix.org") */ - private suspend fun findClientConfig(domain: String): WellknownResult { - val wellKnownAPI = retrofitFactory.create(okHttpClient, "https://dummy.org") + private suspend fun findClientConfig(domain: String, client: OkHttpClient): WellknownResult { + val wellKnownAPI = retrofitFactory.create(client, "https://dummy.org") .create(WellKnownAPI::class.java) return try { @@ -84,7 +99,7 @@ internal class DefaultGetWellknownTask @Inject constructor( } else { if (homeServerBaseUrl.isValidUrl()) { // Check that HS is a real one - validateHomeServer(homeServerBaseUrl, wellKnown) + validateHomeServer(homeServerBaseUrl, wellKnown, client) } else { WellknownResult.FailError } @@ -113,8 +128,8 @@ internal class DefaultGetWellknownTask @Inject constructor( /** * Return true if home server is valid, and (if applicable) if identity server is pingable */ - private suspend fun validateHomeServer(homeServerBaseUrl: String, wellKnown: WellKnown): WellknownResult { - val capabilitiesAPI = retrofitFactory.create(okHttpClient, homeServerBaseUrl) + private suspend fun validateHomeServer(homeServerBaseUrl: String, wellKnown: WellKnown, client: OkHttpClient): WellknownResult { + val capabilitiesAPI = retrofitFactory.create(client, homeServerBaseUrl) .create(CapabilitiesAPI::class.java) try { @@ -135,7 +150,7 @@ internal class DefaultGetWellknownTask @Inject constructor( WellknownResult.FailError } else { if (identityServerBaseUrl.isValidUrl()) { - if (validateIdentityServer(identityServerBaseUrl)) { + if (validateIdentityServer(identityServerBaseUrl, client)) { // All is ok WellknownResult.Prompt(homeServerBaseUrl, identityServerBaseUrl, wellKnown) } else { @@ -151,8 +166,8 @@ internal class DefaultGetWellknownTask @Inject constructor( /** * Return true if identity server is pingable */ - private suspend fun validateIdentityServer(identityServerBaseUrl: String): Boolean { - val identityPingApi = retrofitFactory.create(okHttpClient, identityServerBaseUrl) + private suspend fun validateIdentityServer(identityServerBaseUrl: String, client: OkHttpClient): Boolean { + val identityPingApi = retrofitFactory.create(client, identityServerBaseUrl) .create(IdentityAuthAPI::class.java) return try { diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index 1036a1fd0a..fc970297d1 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -496,7 +496,8 @@ class LoginViewModel @AssistedInject constructor( ) } - authenticationService.getWellKnownData(action.username, object : MatrixCallback { + // TODO Handle certificate error in this case. Direct login is deactivated now, so we will handle that later + authenticationService.getWellKnownData(action.username, null, object : MatrixCallback { override fun onSuccess(data: WellknownResult) { when (data) { is WellknownResult.Prompt -> From cec5cd864c06ab0e1f029998efa7e59eb5599c47 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 30 Jun 2020 12:04:58 +0200 Subject: [PATCH 5/5] Remove legacy class, we do not need them for the migration Migration tested again and OK --- .../internal/legacy/riot/CertUtil.java | 284 ------------------ .../internal/legacy/riot/Fingerprint.java | 38 --- .../legacy/riot/PinnedTrustManager.java | 107 ------- .../legacy/riot/TLSSocketFactory.java | 135 --------- .../UnrecognizedCertificateException.java | 47 --- 5 files changed, 611 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/CertUtil.java delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/PinnedTrustManager.java delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/TLSSocketFactory.java delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/UnrecognizedCertificateException.java diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/CertUtil.java b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/CertUtil.java deleted file mode 100644 index 6c48eddad8..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/CertUtil.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.matrix.android.internal.legacy.riot; - -import android.util.Pair; - -import androidx.annotation.NonNull; - -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import okhttp3.CipherSuite; -import okhttp3.ConnectionSpec; -import okhttp3.TlsVersion; -import timber.log.Timber; - -/* - * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose - */ - -/** - * Various utility classes for dealing with X509Certificates - */ -public class CertUtil { - /** - * Generates the SHA-256 fingerprint of the given certificate - * - * @param cert the certificate. - * @return the finger print - * @throws CertificateException the certificate exception - */ - public static byte[] generateSha256Fingerprint(X509Certificate cert) throws CertificateException { - return generateFingerprint(cert, "SHA-256"); - } - - /** - * Generates the SHA-1 fingerprint of the given certificate - * - * @param cert the certificated - * @return the SHA1 fingerprint - * @throws CertificateException the certificate exception - */ - public static byte[] generateSha1Fingerprint(X509Certificate cert) throws CertificateException { - return generateFingerprint(cert, "SHA-1"); - } - - /** - * Generate the fingerprint for a dedicated type. - * - * @param cert the certificate - * @param type the type - * @return the fingerprint - * @throws CertificateException certificate exception - */ - private static byte[] generateFingerprint(X509Certificate cert, String type) throws CertificateException { - final byte[] fingerprint; - final MessageDigest md; - try { - md = MessageDigest.getInstance(type); - } catch (Exception e) { - // This really *really* shouldn't throw, as java should always have a SHA-256 and SHA-1 impl. - throw new CertificateException(e); - } - - fingerprint = md.digest(cert.getEncoded()); - - return fingerprint; - } - - final private static char[] hexArray = "0123456789ABCDEF".toCharArray(); - - /** - * Convert the fingerprint to an hexa string. - * - * @param fingerprint the fingerprint - * @return the hexa string. - */ - public static String fingerprintToHexString(byte[] fingerprint) { - return fingerprintToHexString(fingerprint, ' '); - } - - public static String fingerprintToHexString(byte[] fingerprint, char sep) { - char[] hexChars = new char[fingerprint.length * 3]; - for (int j = 0; j < fingerprint.length; j++) { - int v = fingerprint[j] & 0xFF; - hexChars[j * 3] = hexArray[v >>> 4]; - hexChars[j * 3 + 1] = hexArray[v & 0x0F]; - hexChars[j * 3 + 2] = sep; - } - return new String(hexChars, 0, hexChars.length - 1); - } - - /** - * Recursively checks the exception to see if it was caused by an - * UnrecognizedCertificateException - * - * @param e the throwable. - * @return The UnrecognizedCertificateException if exists, else null. - */ - public static UnrecognizedCertificateException getCertificateException(Throwable e) { - int i = 0; // Just in case there is a getCause loop - while (e != null && i < 10) { - if (e instanceof UnrecognizedCertificateException) { - return (UnrecognizedCertificateException) e; - } - e = e.getCause(); - i++; - } - - return null; - } - - /** - * Create a SSLSocket factory for a HS config. - * - * @param hsConfig the HS config. - * @return SSLSocket factory - */ - public static Pair newPinnedSSLSocketFactory(HomeServerConnectionConfig hsConfig) { - X509TrustManager defaultTrustManager = null; - - // If we haven't specified that we wanted to pin the certs, fallback to standard - // X509 checks if fingerprints don't match. - if (!hsConfig.shouldPin()) { - TrustManagerFactory trustManagerFactory = null; - - // get the PKIX instance - try { - trustManagerFactory = TrustManagerFactory.getInstance("PKIX"); - } catch (NoSuchAlgorithmException e) { - Timber.e(e, "## newPinnedSSLSocketFactory() : TrustManagerFactory.getInstance failed"); - } - - // it doesn't exist, use the default one. - if (trustManagerFactory == null) { - try { - trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - } catch (NoSuchAlgorithmException e) { - Timber.e(e, "## newPinnedSSLSocketFactory() : TrustManagerFactory.getInstance with default algorithm failed"); - } - } - - if (trustManagerFactory != null) { - try { - trustManagerFactory.init((KeyStore) null); - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - - for (int i = 0; i < trustManagers.length; i++) { - if (trustManagers[i] instanceof X509TrustManager) { - defaultTrustManager = (X509TrustManager) trustManagers[i]; - break; - } - } - } catch (KeyStoreException e) { - Timber.e(e, "## newPinnedSSLSocketFactory()"); - } - } - } - - X509TrustManager trustManager = new PinnedTrustManager(hsConfig.getAllowedFingerprints(), defaultTrustManager); - - TrustManager[] trustManagers = new TrustManager[]{ - trustManager - }; - - SSLSocketFactory sslSocketFactory; - - try { - if (hsConfig.forceUsageOfTlsVersions() && hsConfig.getAcceptedTlsVersions() != null) { - // Force usage of accepted Tls Versions for Android < 20 - sslSocketFactory = new TLSSocketFactory(trustManagers, hsConfig.getAcceptedTlsVersions()); - } else { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, trustManagers, new java.security.SecureRandom()); - sslSocketFactory = sslContext.getSocketFactory(); - } - } catch (Exception e) { - // This is too fatal - throw new RuntimeException(e); - } - - return new Pair<>(sslSocketFactory, trustManager); - } - - /** - * Create a Host name verifier for a hs config. - * - * @param hsConfig the hs config. - * @return a new HostnameVerifier. - */ - public static HostnameVerifier newHostnameVerifier(HomeServerConnectionConfig hsConfig) { - final HostnameVerifier defaultVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); - final List trusted_fingerprints = hsConfig.getAllowedFingerprints(); - - return new HostnameVerifier() { - @Override - public boolean verify(String hostname, SSLSession session) { - if (defaultVerifier.verify(hostname, session)) return true; - if (trusted_fingerprints == null || trusted_fingerprints.size() == 0) return false; - - // If remote cert matches an allowed fingerprint, just accept it. - try { - for (Certificate cert : session.getPeerCertificates()) { - for (Fingerprint allowedFingerprint : trusted_fingerprints) { - if (allowedFingerprint != null && cert instanceof X509Certificate && allowedFingerprint.matchesCert((X509Certificate) cert)) { - return true; - } - } - } - } catch (SSLPeerUnverifiedException e) { - return false; - } catch (CertificateException e) { - return false; - } - - return false; - } - }; - } - - /** - * Create a list of accepted TLS specifications for a hs config. - * - * @param hsConfig the hs config. - * @param url the url of the end point, used to check if we have to enable CLEARTEXT communication. - * @return a list of accepted TLS specifications. - */ - public static List newConnectionSpecs(@NonNull HomeServerConnectionConfig hsConfig, @NonNull String url) { - final ConnectionSpec.Builder builder = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS); - - final List tlsVersions = hsConfig.getAcceptedTlsVersions(); - if (null != tlsVersions) { - builder.tlsVersions(tlsVersions.toArray(new TlsVersion[0])); - } - - final List tlsCipherSuites = hsConfig.getAcceptedTlsCipherSuites(); - if (null != tlsCipherSuites) { - builder.cipherSuites(tlsCipherSuites.toArray(new CipherSuite[0])); - } - - builder.supportsTlsExtensions(hsConfig.shouldAcceptTlsExtensions()); - - List list = new ArrayList<>(); - - list.add(builder.build()); - - if (url.startsWith("http://")) { - list.add(ConnectionSpec.CLEARTEXT); - } - - return list; - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/Fingerprint.java b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/Fingerprint.java index 2bfe7d6e32..bee345e42f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/Fingerprint.java +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/Fingerprint.java @@ -21,8 +21,6 @@ import android.util.Base64; import org.json.JSONException; import org.json.JSONObject; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; import java.util.Arrays; /* @@ -40,20 +38,10 @@ public class Fingerprint { private final HashType mHashType; private final byte[] mBytes; - private String mDisplayableHexRepr; public Fingerprint(HashType hashType, byte[] bytes) { mHashType = hashType; mBytes = bytes; - mDisplayableHexRepr = null; - } - - public static Fingerprint newSha256Fingerprint(X509Certificate cert) throws CertificateException { - return new Fingerprint(HashType.SHA256, CertUtil.generateSha256Fingerprint(cert)); - } - - public static Fingerprint newSha1Fingerprint(X509Certificate cert) throws CertificateException { - return new Fingerprint(HashType.SHA1, CertUtil.generateSha1Fingerprint(cert)); } public HashType getType() { @@ -64,14 +52,6 @@ public class Fingerprint { return mBytes; } - public String getBytesAsHexString() { - if (mDisplayableHexRepr == null) { - mDisplayableHexRepr = CertUtil.fingerprintToHexString(mBytes); - } - - return mDisplayableHexRepr; - } - public JSONObject toJson() throws JSONException { JSONObject obj = new JSONObject(); obj.put("bytes", Base64.encodeToString(getBytes(), Base64.DEFAULT)); @@ -95,24 +75,6 @@ public class Fingerprint { return new Fingerprint(hashType, fingerprintBytes); } - public boolean matchesCert(X509Certificate cert) throws CertificateException { - Fingerprint o = null; - switch (mHashType) { - case SHA256: - o = Fingerprint.newSha256Fingerprint(cert); - break; - case SHA1: - o = Fingerprint.newSha1Fingerprint(cert); - break; - } - - return equals(o); - } - - public String toString() { - return String.format("Fingerprint{type: '%s', fingeprint: '%s'}", mHashType.toString(), getBytesAsHexString()); - } - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/PinnedTrustManager.java b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/PinnedTrustManager.java deleted file mode 100644 index e914bfb724..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/PinnedTrustManager.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.matrix.android.internal.legacy.riot; - -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.List; - -import javax.annotation.Nullable; -import javax.net.ssl.X509TrustManager; - -/* - * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose - */ - -/** - * Implements a TrustManager that checks Certificates against an explicit list of known - * fingerprints. - */ -public class PinnedTrustManager implements X509TrustManager { - private final List mFingerprints; - @Nullable - private final X509TrustManager mDefaultTrustManager; - - /** - * @param fingerprints An array of SHA256 cert fingerprints - * @param defaultTrustManager Optional trust manager to fall back on if cert does not match - * any of the fingerprints. Can be null. - */ - public PinnedTrustManager(List fingerprints, @Nullable X509TrustManager defaultTrustManager) { - mFingerprints = fingerprints; - mDefaultTrustManager = defaultTrustManager; - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String s) throws CertificateException { - try { - if (mDefaultTrustManager != null) { - mDefaultTrustManager.checkClientTrusted( - chain, s - ); - return; - } - } catch (CertificateException e) { - // If there is an exception we fall back to checking fingerprints - if (mFingerprints == null || mFingerprints.size() == 0) { - throw new UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.getCause()); - } - } - checkTrusted("client", chain); - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String s) throws CertificateException { - try { - if (mDefaultTrustManager != null) { - mDefaultTrustManager.checkServerTrusted( - chain, s - ); - return; - } - } catch (CertificateException e) { - // If there is an exception we fall back to checking fingerprints - if (mFingerprints == null || mFingerprints.isEmpty()) { - throw new UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.getCause()); - } - } - checkTrusted("server", chain); - } - - private void checkTrusted(String type, X509Certificate[] chain) throws CertificateException { - X509Certificate cert = chain[0]; - - boolean found = false; - if (mFingerprints != null) { - for (Fingerprint allowedFingerprint : mFingerprints) { - if (allowedFingerprint != null && allowedFingerprint.matchesCert(cert)) { - found = true; - break; - } - } - } - - if (!found) { - throw new UnrecognizedCertificateException(cert, Fingerprint.newSha256Fingerprint(cert), null); - } - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/TLSSocketFactory.java b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/TLSSocketFactory.java deleted file mode 100644 index 6a5921a82d..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/TLSSocketFactory.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.matrix.android.internal.legacy.riot; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.net.UnknownHostException; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; - -import okhttp3.TlsVersion; -import timber.log.Timber; - -/* - * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose - */ - -/** - * Force the usage of Tls versions on every created socket - * Inspired from https://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/ - */ -/*package*/ class TLSSocketFactory extends SSLSocketFactory { - private SSLSocketFactory internalSSLSocketFactory; - - private String[] enabledProtocols; - - /** - * Constructor - * - * @param trustPinned - * @param acceptedTlsVersions - * @throws KeyManagementException - * @throws NoSuchAlgorithmException - */ - /*package*/ TLSSocketFactory(TrustManager[] trustPinned, List acceptedTlsVersions) throws KeyManagementException, NoSuchAlgorithmException { - SSLContext context = SSLContext.getInstance("TLS"); - context.init(null, trustPinned, new SecureRandom()); - internalSSLSocketFactory = context.getSocketFactory(); - - enabledProtocols = new String[acceptedTlsVersions.size()]; - int i = 0; - for (TlsVersion tlsVersion : acceptedTlsVersions) { - enabledProtocols[i] = tlsVersion.javaName(); - i++; - } - } - - @Override - public String[] getDefaultCipherSuites() { - return internalSSLSocketFactory.getDefaultCipherSuites(); - } - - @Override - public String[] getSupportedCipherSuites() { - return internalSSLSocketFactory.getSupportedCipherSuites(); - } - - @Override - public Socket createSocket() throws IOException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket()); - } - - @Override - public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose)); - } - - @Override - public Socket createSocket(String host, int port) throws IOException, UnknownHostException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); - } - - @Override - public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort)); - } - - @Override - public Socket createSocket(InetAddress host, int port) throws IOException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); - } - - @Override - public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort)); - } - - private Socket enableTLSOnSocket(Socket socket) { - if (socket != null && (socket instanceof SSLSocket)) { - SSLSocket sslSocket = (SSLSocket) socket; - - List supportedProtocols = Arrays.asList(sslSocket.getSupportedProtocols()); - List filteredEnabledProtocols = new ArrayList<>(); - - for (String protocol : enabledProtocols) { - if (supportedProtocols.contains(protocol)) { - filteredEnabledProtocols.add(protocol); - } - } - - if (!filteredEnabledProtocols.isEmpty()) { - try { - sslSocket.setEnabledProtocols(filteredEnabledProtocols.toArray(new String[filteredEnabledProtocols.size()])); - } catch (Exception e) { - Timber.e(e, "Exception"); - } - } - } - return socket; - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/UnrecognizedCertificateException.java b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/UnrecognizedCertificateException.java deleted file mode 100644 index 518989a272..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/riot/UnrecognizedCertificateException.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.matrix.android.internal.legacy.riot; - -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -/* - * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose - */ - -/** - * Thrown when we are given a certificate that does match the certificate we were told to - * expect. - */ -public class UnrecognizedCertificateException extends CertificateException { - private final X509Certificate mCert; - private final Fingerprint mFingerprint; - - public UnrecognizedCertificateException(X509Certificate cert, Fingerprint fingerprint, Throwable cause) { - super("Unrecognized certificate with unknown fingerprint: " + cert.getSubjectDN(), cause); - mCert = cert; - mFingerprint = fingerprint; - } - - public X509Certificate getCertificate() { - return mCert; - } - - public Fingerprint getFingerprint() { - return mFingerprint; - } -}