1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 00:02:48 +02:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Jonny Andrew
18a8aded2e Implement trust-on-first-use for CA certs 2023-02-27 17:34:57 +00:00
Jonny Andrew
3437e4cad8 Remove hardcoded pinning configuration 2023-02-27 17:34:57 +00:00
Jonny Andrew
6348827ab2 Store newly trusted certificate [wip] 2023-02-27 17:34:56 +00:00
Jonny Andrew
99e68ac7be Detect cert change in the background 2023-02-27 17:34:56 +00:00
Jonny Andrew
c54b76eb51 Force cert pinning enabled 2023-02-27 17:34:56 +00:00
Jonny Andrew
cfbfa9c679 Fix certificate pinning 2023-02-27 17:34:56 +00:00
Jonny Andrew
aead377003 Fix hostname verification when pinning is enabled 2023-02-27 17:34:55 +00:00
Jonny Andrew
2b0f3832dc Add certificate factory for debugging 2023-02-27 17:34:55 +00:00
26 changed files with 411 additions and 154 deletions

View File

@@ -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 -->

View File

@@ -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)
)
}
}

View File

@@ -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,

View File

@@ -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")))

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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++

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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?

View File

@@ -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) }) {

View File

@@ -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)
}
}

View File

@@ -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,
)
}
}

View File

@@ -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
)
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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 -> {

View File

@@ -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]

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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() {