mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 00:02:48 +02:00
Compare commits
8 Commits
v1.6.46
...
jonny/poc/
Author | SHA1 | Date | |
---|---|---|---|
|
18a8aded2e | ||
|
3437e4cad8 | ||
|
6348827ab2 | ||
|
99e68ac7be | ||
|
c54b76eb51 | ||
|
cfbfa9c679 | ||
|
aead377003 | ||
|
2b0f3832dc |
@@ -735,10 +735,12 @@
|
||||
<string name="ssl_remain_offline">Ignore</string>
|
||||
<string name="ssl_fingerprint_hash">Fingerprint (%s):</string>
|
||||
<string name="ssl_could_not_verify">Could not verify identity of remote server.</string>
|
||||
<string name="ssl_ca_cert_not_trusted">Certificate authority is not yet trusted</string>
|
||||
<string name="ssl_cert_not_trust">This could mean that someone is maliciously intercepting your traffic, or that your phone does not trust the certificate provided by the remote server.</string>
|
||||
<string name="ssl_cert_new_account_expl">If the server administrator has said that this is expected, ensure that the fingerprint below matches the fingerprint provided by them.</string>
|
||||
<string name="ssl_unexpected_existing_expl">The certificate has changed from one that was trusted by your phone. This is HIGHLY UNUSUAL. It is recommended that you DO NOT ACCEPT this new certificate.</string>
|
||||
<string name="ssl_expected_existing_expl">The certificate has changed from a previously trusted one to one that is not trusted. The server may have renewed its certificate. Contact the server administrator for the expected fingerprint.</string>
|
||||
<string name="ssl_ca_change_expl">The certificate was signed by a different certificate authority than previously seen (if any). Contact the server administrator for the expected fingerprint.</string>
|
||||
<string name="ssl_only_accept">Only accept the certificate if the server administrator has published a fingerprint that matches the one above.</string>
|
||||
|
||||
<!-- Room Permissions -->
|
||||
|
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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 org.matrix.android.sdk.api.auth.certs
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.matrix.android.sdk.api.network.ssl.Fingerprint
|
||||
import org.matrix.android.sdk.internal.di.MatrixScope
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Object to store and retrieve home and identity server urls.
|
||||
*/
|
||||
@MatrixScope
|
||||
class TrustedCertificateRepository @Inject constructor(
|
||||
context: Context
|
||||
) {
|
||||
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
|
||||
|
||||
companion object {
|
||||
private const val CURRENT_TRUSTED_CERT_FINGERPRINT_PREF = "current_trusted_certificate_fingerprint"
|
||||
private const val CURRENT_TRUSTED_CERT_HASH_TYPE_PREF = "current_trusted_certificate_hash_type"
|
||||
|
||||
}
|
||||
|
||||
fun updateCurTrustedCert(fingerprint: Fingerprint) {
|
||||
val base64Fingerprint = Base64.encodeToString(fingerprint.bytes, Base64.DEFAULT)
|
||||
sharedPreferences
|
||||
.edit {
|
||||
putString(CURRENT_TRUSTED_CERT_FINGERPRINT_PREF, base64Fingerprint)
|
||||
putString(CURRENT_TRUSTED_CERT_HASH_TYPE_PREF, fingerprint.hashType.name)
|
||||
}
|
||||
}
|
||||
|
||||
fun getCurTrustedCert(): Fingerprint? {
|
||||
val base64Fingerprint = sharedPreferences.getString(
|
||||
CURRENT_TRUSTED_CERT_FINGERPRINT_PREF,
|
||||
null,
|
||||
) ?: return null
|
||||
|
||||
val hashType = sharedPreferences.getString(
|
||||
CURRENT_TRUSTED_CERT_HASH_TYPE_PREF,
|
||||
null,
|
||||
) ?: return null
|
||||
|
||||
return Fingerprint(
|
||||
bytes = Base64.decode(base64Fingerprint, Base64.DEFAULT),
|
||||
hashType = Fingerprint.HashType.valueOf(hashType)
|
||||
)
|
||||
}
|
||||
}
|
@@ -40,6 +40,9 @@ data class HomeServerConnectionConfig(
|
||||
val homeServerUriBase: Uri = homeServerUri,
|
||||
val identityServerUri: Uri? = null,
|
||||
val antiVirusServerUri: Uri? = null,
|
||||
// Allowed fingerprints can represent either
|
||||
// - a set of pinned certificates (if shouldPin is true), or
|
||||
// - an extra set of certificates to trust (if shouldPin is false)
|
||||
val allowedFingerprints: List<Fingerprint> = emptyList(),
|
||||
val shouldPin: Boolean = false,
|
||||
val tlsVersions: List<TlsVersion>? = null,
|
||||
|
@@ -32,7 +32,7 @@ import java.io.IOException
|
||||
*/
|
||||
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
||||
data class UnrecognizedCertificateFailure(val url: String, val fingerprint: Fingerprint) : Failure()
|
||||
data class UnrecognizedCertificateFailure(val url: String, val fingerprint: Fingerprint, val isCaCert: Boolean) : 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")))
|
||||
|
@@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.network.ssl.Fingerprint
|
||||
sealed class GlobalError {
|
||||
data class InvalidToken(val softLogout: Boolean) : GlobalError()
|
||||
data class ConsentNotGivenError(val consentUri: String) : GlobalError()
|
||||
data class CertificateError(val fingerprint: Fingerprint) : GlobalError()
|
||||
data class CertificateError(val fingerprint: Fingerprint, val isCaCert: Boolean) : GlobalError()
|
||||
|
||||
/**
|
||||
* The SDK requires the app (which should request the user) to perform an initial sync.
|
||||
|
@@ -20,6 +20,7 @@ import androidx.annotation.MainThread
|
||||
import io.realm.RealmConfiguration
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.auth.certs.TrustedCertificateRepository
|
||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
import org.matrix.android.sdk.api.federation.FederationService
|
||||
@@ -325,4 +326,5 @@ interface Session {
|
||||
* Debug API, return the list of all RealmConfiguration used by this session.
|
||||
*/
|
||||
fun getRealmConfigurations(): List<RealmConfiguration>
|
||||
fun trustedCertificateRepository(): TrustedCertificateRepository
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.MatrixPatterns.getServerName
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.LoginType
|
||||
import org.matrix.android.sdk.api.auth.SSOAction
|
||||
import org.matrix.android.sdk.api.auth.certs.TrustedCertificateRepository
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
@@ -66,7 +67,8 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val getWellknownTask: GetWellknownTask,
|
||||
private val directLoginTask: DirectLoginTask,
|
||||
private val qrLoginTokenTask: QrLoginTokenTask
|
||||
private val qrLoginTokenTask: QrLoginTokenTask,
|
||||
private val trustedCertificateRepository: TrustedCertificateRepository,
|
||||
) : AuthenticationService {
|
||||
|
||||
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
|
||||
@@ -154,7 +156,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
},
|
||||
{
|
||||
if (it is UnrecognizedCertificateException) {
|
||||
throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUriBase.toString(), it.fingerprint)
|
||||
throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUriBase.toString(), it.fingerprint, it.isCaCert)
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
@@ -442,7 +444,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient {
|
||||
return okHttpClient.get()
|
||||
.newBuilder()
|
||||
.addSocketFactory(homeServerConnectionConfig)
|
||||
.addSocketFactory(homeServerConnectionConfig, trustedCertificateRepository)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.auth
|
||||
|
||||
import dagger.Lazy
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.auth.certs.TrustedCertificateRepository
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.internal.di.Unauthenticated
|
||||
@@ -37,7 +38,8 @@ internal interface IsValidClientServerApiTask : Task<IsValidClientServerApiTask.
|
||||
internal class DefaultIsValidClientServerApiTask @Inject constructor(
|
||||
@Unauthenticated
|
||||
private val okHttpClient: Lazy<OkHttpClient>,
|
||||
private val retrofitFactory: RetrofitFactory
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val trustedCertificateRepository: TrustedCertificateRepository
|
||||
) : IsValidClientServerApiTask {
|
||||
|
||||
override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean {
|
||||
@@ -68,7 +70,7 @@ internal class DefaultIsValidClientServerApiTask @Inject constructor(
|
||||
private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient {
|
||||
return okHttpClient.get()
|
||||
.newBuilder()
|
||||
.addSocketFactory(homeServerConnectionConfig)
|
||||
.addSocketFactory(homeServerConnectionConfig, trustedCertificateRepository)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.auth.login
|
||||
import dagger.Lazy
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.auth.LoginType
|
||||
import org.matrix.android.sdk.api.auth.certs.TrustedCertificateRepository
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
@@ -47,7 +48,8 @@ internal class DefaultDirectLoginTask @Inject constructor(
|
||||
@Unauthenticated
|
||||
private val okHttpClient: Lazy<OkHttpClient>,
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val sessionCreator: SessionCreator
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val trustedCertificateRepository: TrustedCertificateRepository,
|
||||
) : DirectLoginTask {
|
||||
|
||||
override suspend fun execute(params: DirectLoginTask.Params): Session {
|
||||
@@ -72,7 +74,8 @@ internal class DefaultDirectLoginTask @Inject constructor(
|
||||
throw when (throwable) {
|
||||
is UnrecognizedCertificateException -> Failure.UnrecognizedCertificateFailure(
|
||||
homeServerUrl,
|
||||
throwable.fingerprint
|
||||
throwable.fingerprint,
|
||||
throwable.isCaCert,
|
||||
)
|
||||
else -> throwable
|
||||
}
|
||||
@@ -84,7 +87,7 @@ internal class DefaultDirectLoginTask @Inject constructor(
|
||||
private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient {
|
||||
return okHttpClient.get()
|
||||
.newBuilder()
|
||||
.addSocketFactory(homeServerConnectionConfig)
|
||||
.addSocketFactory(homeServerConnectionConfig, trustedCertificateRepository)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.auth.login
|
||||
import dagger.Lazy
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.auth.LoginType
|
||||
import org.matrix.android.sdk.api.auth.certs.TrustedCertificateRepository
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
@@ -47,6 +48,7 @@ internal class DefaultQrLoginTokenTask @Inject constructor(
|
||||
private val okHttpClient: Lazy<OkHttpClient>,
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val trustedCertificateRepository: TrustedCertificateRepository,
|
||||
) : QrLoginTokenTask {
|
||||
|
||||
override suspend fun execute(params: QrLoginTokenTask.Params): Session {
|
||||
@@ -70,7 +72,8 @@ internal class DefaultQrLoginTokenTask @Inject constructor(
|
||||
throw when (throwable) {
|
||||
is UnrecognizedCertificateException -> Failure.UnrecognizedCertificateFailure(
|
||||
homeServerUrl,
|
||||
throwable.fingerprint
|
||||
throwable.fingerprint,
|
||||
throwable.isCaCert,
|
||||
)
|
||||
else -> throwable
|
||||
}
|
||||
@@ -82,7 +85,7 @@ internal class DefaultQrLoginTokenTask @Inject constructor(
|
||||
private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient {
|
||||
return okHttpClient.get()
|
||||
.newBuilder()
|
||||
.addSocketFactory(homeServerConnectionConfig)
|
||||
.addSocketFactory(homeServerConnectionConfig, trustedCertificateRepository)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
@@ -70,11 +70,15 @@ internal suspend inline fun <DATA> executeRequest(
|
||||
|
||||
// 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 ->
|
||||
// Send the error to the bus, for a global management
|
||||
globalErrorReceiver?.handleGlobalError(
|
||||
GlobalError.CertificateError(
|
||||
unrecognizedCertificateException.fingerprint,
|
||||
unrecognizedCertificateException.isCaCert
|
||||
)
|
||||
)
|
||||
}
|
||||
?.also { unrecognizedCertificateException -> throw unrecognizedCertificateException }
|
||||
|
||||
currentRetryCount++
|
||||
|
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.network.httpclient
|
||||
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||
import org.matrix.android.sdk.api.auth.certs.TrustedCertificateRepository
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.internal.network.AccessTokenInterceptor
|
||||
import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingInterceptor
|
||||
@@ -40,11 +41,11 @@ internal fun OkHttpClient.Builder.addAccessTokenInterceptor(accessTokenProvider:
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun OkHttpClient.Builder.addSocketFactory(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient.Builder {
|
||||
internal fun OkHttpClient.Builder.addSocketFactory(homeServerConnectionConfig: HomeServerConnectionConfig, trustedCertificateRepository: TrustedCertificateRepository): OkHttpClient.Builder {
|
||||
try {
|
||||
val pair = CertUtil.newPinnedSSLSocketFactory(homeServerConnectionConfig)
|
||||
val pair = CertUtil.newPinnedSSLSocketFactory(homeServerConnectionConfig, trustedCertificateRepository)
|
||||
sslSocketFactory(pair.sslSocketFactory, pair.x509TrustManager)
|
||||
hostnameVerifier(CertUtil.newHostnameVerifier(homeServerConnectionConfig))
|
||||
hostnameVerifier(CertUtil.newHostnameVerifier(homeServerConnectionConfig, trustedCertificateRepository))
|
||||
connectionSpecs(CertUtil.newConnectionSpecs(homeServerConnectionConfig))
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "addSocketFactory failed")
|
||||
|
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package org.matrix.android.sdk.internal.network.ssl
|
||||
|
||||
import org.matrix.android.sdk.api.network.ssl.Fingerprint
|
||||
import timber.log.Timber
|
||||
import java.nio.charset.StandardCharsets.UTF_8
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
// TODO: delete this file used for debugging
|
||||
object CertFactory {
|
||||
const val USE_FAKE_CERT = true
|
||||
// matrix.org
|
||||
private const val REAL_CERT = """-----BEGIN CERTIFICATE-----
|
||||
MIIFMjCCBNmgAwIBAgIQBBeWeU5gfMfdXerawPjLJzAKBggqhkjOPQQDAjBKMQsw
|
||||
CQYDVQQGEwJVUzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjEgMB4GA1UEAxMX
|
||||
Q2xvdWRmbGFyZSBJbmMgRUNDIENBLTMwHhcNMjIwNjAzMDAwMDAwWhcNMjMwNjAy
|
||||
MjM1OTU5WjB1MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG
|
||||
A1UEBxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjEe
|
||||
MBwGA1UEAxMVc25pLmNsb3VkZmxhcmVzc2wuY29tMFkwEwYHKoZIzj0CAQYIKoZI
|
||||
zj0DAQcDQgAE8dtavEXS2K4uRm+1Es+J2y6DawGgf7o7Pq3eeWXVmKaMH6mkANzB
|
||||
CWRQPwIHwiY4EIHxJ+rj6cHOx3ZMWLhOVaOCA3QwggNwMB8GA1UdIwQYMBaAFKXO
|
||||
N+rrsHUOlGeItEX62SQQh5YfMB0GA1UdDgQWBBQHH1R1J09Rny+Uow1mStelGJ5G
|
||||
QDA6BgNVHREEMzAxggptYXRyaXgub3JnggwqLm1hdHJpeC5vcmeCFXNuaS5jbG91
|
||||
ZGZsYXJlc3NsLmNvbTAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUH
|
||||
AwEGCCsGAQUFBwMCMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmwzLmRpZ2lj
|
||||
ZXJ0LmNvbS9DbG91ZGZsYXJlSW5jRUNDQ0EtMy5jcmwwN6A1oDOGMWh0dHA6Ly9j
|
||||
cmw0LmRpZ2ljZXJ0LmNvbS9DbG91ZGZsYXJlSW5jRUNDQ0EtMy5jcmwwPgYDVR0g
|
||||
BDcwNTAzBgZngQwBAgIwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2Vy
|
||||
dC5jb20vQ1BTMHYGCCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29j
|
||||
c3AuZGlnaWNlcnQuY29tMEAGCCsGAQUFBzAChjRodHRwOi8vY2FjZXJ0cy5kaWdp
|
||||
Y2VydC5jb20vQ2xvdWRmbGFyZUluY0VDQ0NBLTMuY3J0MAwGA1UdEwEB/wQCMAAw
|
||||
ggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB2AOg+0No+9QY1MudXKLyJa8kD08vR
|
||||
EWvs62nhd31tBr1uAAABgSj8RDsAAAQDAEcwRQIgeKrerQniRck5d4Z6znAukXfy
|
||||
J9UkueqAgMFbRIwtUpsCIQD80xXpZ0fVpRizbsaqLtaVoauUfMjVHaY8pJn6iq/R
|
||||
LQB2ADXPGRu/sWxXvw+tTG1Cy7u2JyAmUeo/4SrvqAPDO9ZMAAABgSj8REIAAAQD
|
||||
AEcwRQIgBjnCX2/hZeblE8/7oyY3DMqQKAXL2GViwjqKtdpd6HMCIQDLqK5sPNX3
|
||||
aEq0a+U1j9THuE8TRcaNDhsa/J1dPIN3xgB2ALNzdwfhhFD4Y4bWBancEQlKeS2x
|
||||
ZwwLh9zwAw55NqWaAAABgSj8RH8AAAQDAEcwRQIhANNxszQajjCzVJFmrt9csXx/
|
||||
JMHlPuLDCe0OOQSmNwgGAiAaBdLaFHDCZcwu0XWygrZa2PVuA6V1Rx8TRh1OIkI8
|
||||
0TAKBggqhkjOPQQDAgNHADBEAiAYvhguHG93pulpDLIx8m/nQjrlJ3XIE3EJvCc/
|
||||
7eqYIQIgCTFkBsfnOrMNDMmpKThKmZLeN+rCzRimNJzVrA9FoUA=
|
||||
-----END CERTIFICATE-----"""
|
||||
|
||||
// matrix.org self signed
|
||||
private const val FAKE_CERT = """-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIUXrfdDbZtpn9HealS9lniLu21fPwwDQYJKoZIhvcNAQEL
|
||||
BQAwFTETMBEGA1UEAwwKbWF0cml4Lm9yZzAeFw0yMzAyMTYxNTA4MDJaFw0yNDAy
|
||||
MTYxNTA4MDJaMBUxEzARBgNVBAMMCm1hdHJpeC5vcmcwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQCUSHOv+arN7NL1IA0c1v9mlmNTaVIzIQHoWwAv5DAI
|
||||
3EutVjjWKAukGRU8ZlGMzA3lN9Ho21Rzn0+G/8tuMEr+msO153n/AUU8fGLJa5zX
|
||||
LNrOn7bg0KtGBuCtZOowmT7zWS73OBLJqC5F3cTtkQc9nKdOf50xfI/gVBcwwEZ5
|
||||
PXyCxSOYvXyryOkStHL70sNfMAAwrznObtT2+W6VGn31A86QWiMKC7flbHL0n+1D
|
||||
mXSAANsMYH3fYMD8wbaoBDhF4GRt1okT7MV6AFvYNk6FCF6N9cVuRipKc7USYEcq
|
||||
JydBiYH1a6bHhisRNTb9ZEnwO6BDeVE87Q7nJjYUUperAgMBAAGjUzBRMB0GA1Ud
|
||||
DgQWBBTyJ6kHBTo/nyeC8jQQmW/bO8afXzAfBgNVHSMEGDAWgBTyJ6kHBTo/nyeC
|
||||
8jQQmW/bO8afXzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCA
|
||||
FkPAiDPM9vt5qw46Vu4obLJMN3CjvOnnWx3Ydj2JmN7uLh6rKYO//r7oQLm6Mxhx
|
||||
6RxuwdTkUdmG2q4Vb7UkmGHFDyN5uYu+QMHI8Iqj4lBcGZ6GxTb8bb3tptFhywmr
|
||||
JF5yRBS81VcqPxdNrXgcUcwXM5ai1dhW7IJC9xQ4I/hLsr84gW5qAF509uOQcX0C
|
||||
vYpowVMQTzhvaeOnOZIIAQbEC8mVQ2CvGQxJWK1md6Q2kh5vEZTrtSHTdCDogxco
|
||||
GcpkZPllI0egxNBjgFq6TBlnUlc0Vusv6aQP4qU0M1qWzNNtwGyTthQ2aG5ELrlk
|
||||
S23krUKHHamt3FH6dZiC
|
||||
-----END CERTIFICATE-----"""
|
||||
|
||||
fun createStaticFingerprint() = if (USE_FAKE_CERT) {
|
||||
createFakeFingerprint()
|
||||
} else {
|
||||
createRealFingerprint()
|
||||
}
|
||||
|
||||
|
||||
fun createRealFingerprint() =
|
||||
createFingerprint(createCert(REAL_CERT))
|
||||
|
||||
fun createFakeFingerprint() =
|
||||
createFingerprint(createCert(FAKE_CERT))
|
||||
|
||||
private fun createFingerprint(cert: X509Certificate): Fingerprint {
|
||||
val fingerprint = Fingerprint.newSha256Fingerprint(cert)
|
||||
|
||||
Timber.d("## CERT Fingerprint: ${fingerprint.displayableHexRepr}")
|
||||
return fingerprint
|
||||
}
|
||||
|
||||
private fun createCert(certString: String) = CertificateFactory
|
||||
.getInstance("X.509")
|
||||
.generateCertificate(certString.byteInputStream(UTF_8)) as X509Certificate
|
||||
}
|
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.network.ssl
|
||||
|
||||
import okhttp3.ConnectionSpec
|
||||
import okhttp3.internal.tls.OkHostnameVerifier
|
||||
import org.matrix.android.sdk.api.auth.certs.TrustedCertificateRepository
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import timber.log.Timber
|
||||
import java.security.KeyStore
|
||||
@@ -28,7 +29,6 @@ import javax.net.ssl.HostnameVerifier
|
||||
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
|
||||
|
||||
@@ -140,7 +140,7 @@ internal object CertUtil {
|
||||
* @param hsConfig the HS config.
|
||||
* @return SSLSocket factory
|
||||
*/
|
||||
fun newPinnedSSLSocketFactory(hsConfig: HomeServerConnectionConfig): PinnedSSLSocketFactory {
|
||||
fun newPinnedSSLSocketFactory(hsConfig: HomeServerConnectionConfig, trustedCertificateRepository: TrustedCertificateRepository): PinnedSSLSocketFactory {
|
||||
try {
|
||||
var defaultTrustManager: X509TrustManager? = null
|
||||
|
||||
@@ -176,18 +176,18 @@ internal object CertUtil {
|
||||
}
|
||||
}
|
||||
|
||||
val trustPinned = arrayOf<TrustManager>(PinnedTrustManagerProvider.provide(hsConfig.allowedFingerprints, defaultTrustManager))
|
||||
val pinnedTrustManager = PinnedTrustManagerProvider.provide(hsConfig.allowedFingerprints, defaultTrustManager, trustedCertificateRepository)
|
||||
|
||||
val sslSocketFactory = if (hsConfig.forceUsageTlsVersions && !hsConfig.tlsVersions.isNullOrEmpty()) {
|
||||
// Force usage of accepted Tls Versions for Android < 20
|
||||
TLSSocketFactory(trustPinned, hsConfig.tlsVersions)
|
||||
TLSSocketFactory(arrayOf(pinnedTrustManager), hsConfig.tlsVersions)
|
||||
} else {
|
||||
val sslContext = SSLContext.getInstance("TLS")
|
||||
sslContext.init(null, trustPinned, java.security.SecureRandom())
|
||||
sslContext.init(null, arrayOf(pinnedTrustManager), java.security.SecureRandom())
|
||||
sslContext.socketFactory
|
||||
}
|
||||
|
||||
return PinnedSSLSocketFactory(sslSocketFactory, defaultTrustManager!!)
|
||||
return PinnedSSLSocketFactory(sslSocketFactory, pinnedTrustManager)
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
@@ -199,12 +199,13 @@ internal object CertUtil {
|
||||
* @param hsConfig the hs config.
|
||||
* @return a new HostnameVerifier.
|
||||
*/
|
||||
fun newHostnameVerifier(hsConfig: HomeServerConnectionConfig): HostnameVerifier {
|
||||
fun newHostnameVerifier(hsConfig: HomeServerConnectionConfig, trustedCertificateRepository: TrustedCertificateRepository): HostnameVerifier {
|
||||
val defaultVerifier: HostnameVerifier = OkHostnameVerifier // HttpsURLConnection.getDefaultHostnameVerifier()
|
||||
val trustedFingerprints = hsConfig.allowedFingerprints
|
||||
val trustedFingerprints = hsConfig.allowedFingerprints + listOfNotNull(trustedCertificateRepository.getCurTrustedCert())
|
||||
val shouldPin = hsConfig.shouldPin
|
||||
|
||||
return HostnameVerifier { hostname, session ->
|
||||
if (USE_DEFAULT_HOSTNAME_VERIFIER) {
|
||||
if (USE_DEFAULT_HOSTNAME_VERIFIER && !shouldPin) {
|
||||
if (defaultVerifier.verify(hostname, session)) return@HostnameVerifier true
|
||||
}
|
||||
// TODO How to recover from this error?
|
||||
|
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.network.ssl
|
||||
|
||||
import org.matrix.android.sdk.api.auth.certs.TrustedCertificateRepository
|
||||
import org.matrix.android.sdk.api.network.ssl.Fingerprint
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
@@ -25,51 +26,74 @@ import javax.net.ssl.X509TrustManager
|
||||
* Implements a TrustManager that checks Certificates against an explicit list of known
|
||||
* fingerprints.
|
||||
*
|
||||
* @property fingerprints Not empty array of SHA256 cert fingerprints
|
||||
* @property staticFingerprints Not empty array of SHA256 cert fingerprints
|
||||
* @property defaultTrustManager Optional trust manager to fall back on if cert does not match
|
||||
* any of the fingerprints. Can be null.
|
||||
*/
|
||||
internal class PinnedTrustManager(
|
||||
private val fingerprints: List<Fingerprint>,
|
||||
private val defaultTrustManager: X509TrustManager?
|
||||
private val staticFingerprints: List<Fingerprint>,
|
||||
private val defaultTrustManager: X509TrustManager?,
|
||||
private val trustedCertificateRepository: TrustedCertificateRepository,
|
||||
) : X509TrustManager {
|
||||
|
||||
private val fingerprints
|
||||
get() = staticFingerprints +
|
||||
listOfNotNull(trustedCertificateRepository.getCurTrustedCert())
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, s: String) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkClientTrusted(chain, s)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
|
||||
}
|
||||
check(chain) {
|
||||
checkClientTrusted(chain, s)
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, s: String) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkServerTrusted(chain, s)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */)
|
||||
}
|
||||
check(chain) {
|
||||
checkServerTrusted(chain, s)
|
||||
}
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
private fun check(chain: Array<X509Certificate>, defaultCheck: X509TrustManager.() -> Unit) {
|
||||
if (defaultTrustManager != null) {
|
||||
try {
|
||||
defaultTrustManager.defaultCheck()
|
||||
} catch (e: CertificateException) {
|
||||
checkPins(chain)
|
||||
}
|
||||
checkCaTrusted(chain)
|
||||
} else {
|
||||
checkPins(chain)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
private fun checkTrusted(chain: Array<X509Certificate>) {
|
||||
private fun checkCaTrusted(chain: Array<X509Certificate>) {
|
||||
// Get the certificate closest to the root.
|
||||
// This may or may not be the root certificate.
|
||||
val cert = chain[chain.size - 1]
|
||||
|
||||
val root = acceptedIssuers.firstOrNull { issuer ->
|
||||
if (issuer.subjectDN != cert.subjectDN &&
|
||||
issuer.subjectDN != cert.issuerDN)
|
||||
return@firstOrNull false
|
||||
|
||||
try {
|
||||
cert.verify(issuer.publicKey)
|
||||
} catch (e: Exception) {
|
||||
return@firstOrNull false
|
||||
}
|
||||
|
||||
return@firstOrNull true
|
||||
} ?: throw UnrecognizedCertificateException(cert, Fingerprint.newSha256Fingerprint(cert), isCaCert = false, null)
|
||||
|
||||
if (!fingerprints.any { it.matchesCert(root) }) {
|
||||
throw UnrecognizedCertificateException(root, Fingerprint.newSha256Fingerprint(root), isCaCert = true, null)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
private fun checkPins(chain: Array<X509Certificate>) {
|
||||
val cert = chain[0]
|
||||
|
||||
if (!fingerprints.any { it.matchesCert(cert) }) {
|
||||
|
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.network.ssl
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import org.matrix.android.sdk.api.auth.certs.TrustedCertificateRepository
|
||||
import org.matrix.android.sdk.api.network.ssl.Fingerprint
|
||||
import java.net.Socket
|
||||
import java.security.cert.CertificateException
|
||||
@@ -29,123 +30,105 @@ import javax.net.ssl.X509ExtendedTrustManager
|
||||
* Implements a TrustManager that checks Certificates against an explicit list of known
|
||||
* fingerprints.
|
||||
*
|
||||
* @property fingerprints An array of SHA256 cert fingerprints
|
||||
* @property defaultTrustManager Optional trust manager to fall back on if cert does not match
|
||||
* @property staticFingerprints An array of SHA256 cert fingerprints
|
||||
* @property defaultTrustManager Optional truHst manager to fall back on if cert does not match
|
||||
* any of the fingerprints. Can be null.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
internal class PinnedTrustManagerApi24(
|
||||
private val fingerprints: List<Fingerprint>,
|
||||
private val defaultTrustManager: X509ExtendedTrustManager?
|
||||
private val staticFingerprints: List<Fingerprint>,
|
||||
private val defaultTrustManager: X509ExtendedTrustManager?,
|
||||
private val trustedCertificateRepository: TrustedCertificateRepository,
|
||||
) : X509ExtendedTrustManager() {
|
||||
|
||||
private val fingerprints: List<Fingerprint>
|
||||
get() = staticFingerprints +
|
||||
listOfNotNull(trustedCertificateRepository.getCurTrustedCert())
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String, engine: SSLEngine?) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkClientTrusted(chain, authType, engine)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
|
||||
}
|
||||
check(chain) {
|
||||
checkClientTrusted(chain, authType, engine)
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String, socket: Socket?) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkClientTrusted(chain, authType, socket)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
|
||||
}
|
||||
check(chain) {
|
||||
checkClientTrusted(chain, authType, socket)
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkClientTrusted(chain, authType)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
|
||||
}
|
||||
check(chain) {
|
||||
checkClientTrusted(chain, authType)
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String, socket: Socket?) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkServerTrusted(chain, authType, socket)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */)
|
||||
}
|
||||
check(chain) {
|
||||
checkServerTrusted(chain, authType, socket)
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String, engine: SSLEngine?) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkServerTrusted(chain, authType, engine)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */)
|
||||
}
|
||||
check(chain) {
|
||||
checkServerTrusted(chain, authType, engine)
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, s: String) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkServerTrusted(chain, s)
|
||||
return
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, s: String) =
|
||||
check(chain) {
|
||||
checkServerTrusted(chain, s)
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */)
|
||||
}
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
private fun check(chain: Array<X509Certificate>, defaultCheck: X509ExtendedTrustManager.() -> Unit) {
|
||||
if (defaultTrustManager != null) {
|
||||
try {
|
||||
defaultTrustManager.defaultCheck()
|
||||
} catch (e: CertificateException) {
|
||||
checkPins(chain)
|
||||
}
|
||||
checkCaTrusted(chain)
|
||||
} else {
|
||||
checkPins(chain)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
private fun checkTrusted(chain: Array<X509Certificate>) {
|
||||
private fun checkCaTrusted(chain: Array<X509Certificate>) {
|
||||
// Get the certificate closest to the root.
|
||||
// This may or may not be the root certificate.
|
||||
val cert = chain[chain.size - 1]
|
||||
|
||||
val root = acceptedIssuers.firstOrNull { issuer ->
|
||||
if (issuer.subjectDN != cert.subjectDN &&
|
||||
issuer.subjectDN != cert.issuerDN)
|
||||
return@firstOrNull false
|
||||
|
||||
try {
|
||||
cert.verify(issuer.publicKey)
|
||||
} catch (e: Exception) {
|
||||
return@firstOrNull false
|
||||
}
|
||||
|
||||
return@firstOrNull true
|
||||
} ?: throw UnrecognizedCertificateException(cert, Fingerprint.newSha256Fingerprint(cert), isCaCert = false, null)
|
||||
|
||||
if (!fingerprints.any { it.matchesCert(root) }) {
|
||||
throw UnrecognizedCertificateException(root, Fingerprint.newSha256Fingerprint(root), isCaCert = true, null)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
private fun checkPins(chain: Array<X509Certificate>) {
|
||||
val cert = chain[0]
|
||||
|
||||
if (!fingerprints.any { it.matchesCert(cert) }) {
|
||||
if (!staticFingerprints.any { it.matchesCert(cert) }) {
|
||||
throw UnrecognizedCertificateException(cert, Fingerprint.newSha256Fingerprint(cert), null)
|
||||
}
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@
|
||||
package org.matrix.android.sdk.internal.network.ssl
|
||||
|
||||
import android.os.Build
|
||||
import org.matrix.android.sdk.api.auth.certs.TrustedCertificateRepository
|
||||
import org.matrix.android.sdk.api.network.ssl.Fingerprint
|
||||
import javax.net.ssl.X509ExtendedTrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
@@ -27,17 +28,20 @@ internal object PinnedTrustManagerProvider {
|
||||
|
||||
fun provide(
|
||||
fingerprints: List<Fingerprint>?,
|
||||
defaultTrustManager: X509TrustManager?
|
||||
defaultTrustManager: X509TrustManager?,
|
||||
trustedCertificateRepository: TrustedCertificateRepository,
|
||||
): X509TrustManager {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && defaultTrustManager is X509ExtendedTrustManager) {
|
||||
PinnedTrustManagerApi24(
|
||||
fingerprints.orEmpty(),
|
||||
defaultTrustManager.takeIf { USE_DEFAULT_TRUST_MANAGER }
|
||||
defaultTrustManager.takeIf { USE_DEFAULT_TRUST_MANAGER },
|
||||
trustedCertificateRepository,
|
||||
)
|
||||
} else {
|
||||
PinnedTrustManager(
|
||||
fingerprints.orEmpty(),
|
||||
defaultTrustManager.takeIf { USE_DEFAULT_TRUST_MANAGER }
|
||||
defaultTrustManager.takeIf { USE_DEFAULT_TRUST_MANAGER },
|
||||
trustedCertificateRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -27,5 +27,14 @@ import java.security.cert.X509Certificate
|
||||
internal data class UnrecognizedCertificateException(
|
||||
val certificate: X509Certificate,
|
||||
val fingerprint: Fingerprint,
|
||||
val isCaCert: Boolean = false,
|
||||
override val cause: Throwable?
|
||||
) : CertificateException("Unrecognized certificate with unknown fingerprint: " + certificate.subjectDN, cause)
|
||||
) : CertificateException("Unrecognized certificate with unknown fingerprint: " + certificate.subjectDN, cause) {
|
||||
constructor(
|
||||
certificate: X509Certificate,
|
||||
fingerprint: Fingerprint,
|
||||
cause: Throwable?
|
||||
) : this(
|
||||
certificate, fingerprint, false, cause
|
||||
)
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.auth.certs.TrustedCertificateRepository
|
||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
import org.matrix.android.sdk.api.federation.FederationService
|
||||
@@ -82,6 +83,7 @@ import javax.inject.Inject
|
||||
@SessionScope
|
||||
internal class DefaultSession @Inject constructor(
|
||||
override val sessionParams: SessionParams,
|
||||
private val trustedCertificateRepository: TrustedCertificateRepository,
|
||||
private val workManagerProvider: WorkManagerProvider,
|
||||
private val globalErrorHandler: GlobalErrorHandler,
|
||||
@SessionId
|
||||
@@ -230,6 +232,7 @@ internal class DefaultSession @Inject constructor(
|
||||
override fun openIdService(): OpenIdService = openIdService.get()
|
||||
override fun accountDataService(): SessionAccountDataService = accountDataService.get()
|
||||
override fun sharedSecretStorageService(): SharedSecretStorageService = sharedSecretStorageService.get()
|
||||
override fun trustedCertificateRepository(): TrustedCertificateRepository = trustedCertificateRepository
|
||||
|
||||
override fun getOkHttpClient(): OkHttpClient {
|
||||
return unauthenticatedWithCertificateOkHttpClient.get()
|
||||
|
@@ -27,6 +27,7 @@ import dagger.multibindings.IntoSet
|
||||
import io.realm.RealmConfiguration
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||
import org.matrix.android.sdk.api.auth.certs.TrustedCertificateRepository
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||
@@ -213,10 +214,11 @@ internal abstract class SessionModule {
|
||||
fun providesOkHttpClientWithCertificate(
|
||||
@Unauthenticated okHttpClient: OkHttpClient,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
trustedCertificateRepository: TrustedCertificateRepository,
|
||||
): OkHttpClient {
|
||||
return okHttpClient
|
||||
.newBuilder()
|
||||
.addSocketFactory(homeServerConnectionConfig)
|
||||
.addSocketFactory(homeServerConnectionConfig, trustedCertificateRepository)
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -303,6 +305,13 @@ internal abstract class SessionModule {
|
||||
fun providesMxCryptoConfig(matrixConfiguration: MatrixConfiguration): MXCryptoConfig {
|
||||
return matrixConfiguration.cryptoConfig
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
@SessionScope
|
||||
fun providesTrustedCertificateRepository(context: Context): TrustedCertificateRepository {
|
||||
return TrustedCertificateRepository(context)
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
|
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.sync
|
||||
import android.os.SystemClock
|
||||
import okhttp3.ResponseBody
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.statistics.StatisticEvent
|
||||
@@ -33,6 +34,7 @@ import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.TimeOutInterceptor
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.network.ssl.UnrecognizedCertificateException
|
||||
import org.matrix.android.sdk.internal.network.toFailure
|
||||
import org.matrix.android.sdk.internal.session.SessionListeners
|
||||
import org.matrix.android.sdk.internal.session.dispatchTo
|
||||
|
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.wellknown
|
||||
import android.util.MalformedJsonException
|
||||
import dagger.Lazy
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.auth.certs.TrustedCertificateRepository
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.WellKnown
|
||||
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
||||
@@ -53,7 +54,8 @@ internal interface GetWellknownTask : Task<GetWellknownTask.Params, WellknownRes
|
||||
internal class DefaultGetWellknownTask @Inject constructor(
|
||||
@Unauthenticated
|
||||
private val okHttpClient: Lazy<OkHttpClient>,
|
||||
private val retrofitFactory: RetrofitFactory
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val trustedCertificateRepository: TrustedCertificateRepository,
|
||||
) : GetWellknownTask {
|
||||
|
||||
override suspend fun execute(params: GetWellknownTask.Params): WellknownResult {
|
||||
@@ -64,7 +66,7 @@ internal class DefaultGetWellknownTask @Inject constructor(
|
||||
private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient {
|
||||
return okHttpClient.get()
|
||||
.newBuilder()
|
||||
.addSocketFactory(homeServerConnectionConfig)
|
||||
.addSocketFactory(homeServerConnectionConfig, trustedCertificateRepository)
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -104,7 +106,8 @@ internal class DefaultGetWellknownTask @Inject constructor(
|
||||
is UnrecognizedCertificateException -> {
|
||||
throw Failure.UnrecognizedCertificateFailure(
|
||||
"https://$domain",
|
||||
throwable.fingerprint
|
||||
throwable.fingerprint,
|
||||
isCaCert = throwable.isCaCert,
|
||||
)
|
||||
}
|
||||
is Failure.NetworkConnection -> {
|
||||
|
@@ -24,10 +24,12 @@ import im.vector.app.databinding.DialogSslFingerprintBinding
|
||||
import org.matrix.android.sdk.api.network.ssl.Fingerprint
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* This class displays the unknown certificate dialog.
|
||||
*/
|
||||
@Singleton
|
||||
class UnrecognizedCertificateDialog @Inject constructor(
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val stringProvider: StringProvider
|
||||
@@ -46,6 +48,7 @@ class UnrecognizedCertificateDialog @Inject constructor(
|
||||
fun show(
|
||||
activity: Activity,
|
||||
unrecognizedFingerprint: Fingerprint,
|
||||
isCaCert: Boolean,
|
||||
callback: Callback
|
||||
) {
|
||||
val userId = activeSessionHolder.getSafeActiveSession()?.myUserId
|
||||
@@ -54,11 +57,12 @@ class UnrecognizedCertificateDialog @Inject constructor(
|
||||
internalShow(
|
||||
activity = activity,
|
||||
unrecognizedFingerprint = unrecognizedFingerprint,
|
||||
existing = true,
|
||||
isLoggedIn = true,
|
||||
callback = callback,
|
||||
userId = userId,
|
||||
homeServerUrl = hsConfig.homeServerUriBase.toString(),
|
||||
homeServerConnectionConfigHasFingerprints = hsConfig.allowedFingerprints.isNotEmpty()
|
||||
homeServerConnectionConfigHasFingerprints = hsConfig.allowedFingerprints.isNotEmpty(),
|
||||
isCaCert = isCaCert,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -68,17 +72,19 @@ class UnrecognizedCertificateDialog @Inject constructor(
|
||||
fun show(
|
||||
activity: Activity,
|
||||
unrecognizedFingerprint: Fingerprint,
|
||||
isCaCert: Boolean,
|
||||
homeServerUrl: String,
|
||||
callback: Callback
|
||||
) {
|
||||
internalShow(
|
||||
activity = activity,
|
||||
unrecognizedFingerprint = unrecognizedFingerprint,
|
||||
existing = false,
|
||||
isLoggedIn = false,
|
||||
callback = callback,
|
||||
userId = null,
|
||||
homeServerUrl = homeServerUrl,
|
||||
homeServerConnectionConfigHasFingerprints = false
|
||||
homeServerConnectionConfigHasFingerprints = false,
|
||||
isCaCert = isCaCert,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -87,7 +93,7 @@ class UnrecognizedCertificateDialog @Inject constructor(
|
||||
*
|
||||
* @param activity the Activity
|
||||
* @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 isLoggedIn 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
|
||||
* @param userId the matrix userId
|
||||
* @param homeServerUrl the homeserver url
|
||||
@@ -96,13 +102,14 @@ class UnrecognizedCertificateDialog @Inject constructor(
|
||||
private fun internalShow(
|
||||
activity: Activity,
|
||||
unrecognizedFingerprint: Fingerprint,
|
||||
existing: Boolean,
|
||||
isLoggedIn: Boolean,
|
||||
callback: Callback,
|
||||
userId: String?,
|
||||
homeServerUrl: String,
|
||||
homeServerConnectionConfigHasFingerprints: Boolean
|
||||
homeServerConnectionConfigHasFingerprints: Boolean,
|
||||
isCaCert: Boolean,
|
||||
) {
|
||||
val dialogId = userId ?: homeServerUrl + unrecognizedFingerprint.displayableHexRepr
|
||||
val dialogId = userId ?: (homeServerUrl + unrecognizedFingerprint.displayableHexRepr)
|
||||
|
||||
if (openDialogIds.contains(dialogId)) {
|
||||
Timber.i("Not opening dialog $dialogId as one is already open.")
|
||||
@@ -136,7 +143,9 @@ class UnrecognizedCertificateDialog @Inject constructor(
|
||||
homeServerUrl
|
||||
)
|
||||
}
|
||||
if (existing) {
|
||||
if (isCaCert) {
|
||||
views.sslExplanation.text = stringProvider.getString(R.string.ssl_ca_change_expl)
|
||||
} else if (isLoggedIn) {
|
||||
if (homeServerConnectionConfigHasFingerprints) {
|
||||
views.sslExplanation.text = stringProvider.getString(R.string.ssl_expected_existing_expl)
|
||||
} else {
|
||||
@@ -146,11 +155,17 @@ class UnrecognizedCertificateDialog @Inject constructor(
|
||||
views.sslExplanation.text = stringProvider.getString(R.string.ssl_cert_new_account_expl)
|
||||
}
|
||||
builder.setView(layout)
|
||||
builder.setTitle(R.string.ssl_could_not_verify)
|
||||
builder.setTitle(
|
||||
if(isCaCert) {
|
||||
R.string.ssl_ca_cert_not_trusted
|
||||
} else {
|
||||
R.string.ssl_could_not_verify
|
||||
}
|
||||
)
|
||||
builder.setPositiveButton(R.string.ssl_trust) { _, _ ->
|
||||
callback.onAccept()
|
||||
}
|
||||
if (existing) {
|
||||
if (isLoggedIn) {
|
||||
builder.setNegativeButton(R.string.ssl_remain_offline) { _, _ ->
|
||||
if (userId != null) {
|
||||
var f = ignoredFingerprints[userId]
|
||||
|
@@ -171,6 +171,9 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
||||
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||
private val session by lazy {
|
||||
activeSessionHolder.getActiveSession()
|
||||
}
|
||||
|
||||
// For debug only
|
||||
@Inject lateinit var debugReceiver: DebugReceiver
|
||||
@@ -337,13 +340,17 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
}
|
||||
|
||||
private fun handleCertificateError(certificateError: GlobalError.CertificateError) {
|
||||
if(this is MainActivity) {
|
||||
return
|
||||
}
|
||||
singletonEntryPoint()
|
||||
.unrecognizedCertificateDialog()
|
||||
.show(this,
|
||||
certificateError.fingerprint,
|
||||
certificateError.isCaCert,
|
||||
object : UnrecognizedCertificateDialog.Callback {
|
||||
override fun onAccept() {
|
||||
// TODO Support certificate error once logged
|
||||
session.trustedCertificateRepository().updateCurTrustedCert(certificateError.fingerprint)
|
||||
}
|
||||
|
||||
override fun onIgnore() {
|
||||
|
@@ -103,6 +103,7 @@ abstract class AbstractLoginFragment<VB : ViewBinding> : VectorBaseFragment<VB>(
|
||||
// Ask the user to accept the certificate
|
||||
unrecognizedCertificateDialog.show(requireActivity(),
|
||||
failure.fingerprint,
|
||||
failure.isCaCert,
|
||||
failure.url,
|
||||
object : UnrecognizedCertificateDialog.Callback {
|
||||
override fun onAccept() {
|
||||
|
@@ -93,6 +93,7 @@ abstract class AbstractFtueAuthFragment<VB : ViewBinding> : VectorBaseFragment<V
|
||||
val cause = event.cause
|
||||
unrecognizedCertificateDialog.show(requireActivity(),
|
||||
cause.fingerprint,
|
||||
cause.isCaCert,
|
||||
cause.url,
|
||||
object : UnrecognizedCertificateDialog.Callback {
|
||||
override fun onAccept() {
|
||||
|
Reference in New Issue
Block a user