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