Browse Source

Improved SSL handling, enabling TLS 1.1/1.2 (where Android supports it) and supporting SNI hostname verification. Self-signed certificates that are not matched write the correct SHA-1 server hash to use to the Transdroid log. Enabled true SSL certificate checking for the supported seedboxes (Xirvik, SeedStuff). Fixed #127 and fixes #171.

pull/177/head
Eric Kok 10 years ago
parent
commit
6a7cc5c5c6
  1. 2
      app/src/main/java/org/transdroid/core/seedbox/SeedstuffSettings.java
  2. 2
      app/src/main/java/org/transdroid/core/seedbox/XirvikDediSettings.java
  3. 2
      app/src/main/java/org/transdroid/core/seedbox/XirvikSemiSettings.java
  4. 2
      app/src/main/java/org/transdroid/core/seedbox/XirvikSharedSettings.java
  5. 20
      app/src/main/java/org/transdroid/daemon/Vuze/VuzeXmlOverHttpClient.java
  6. 86
      app/src/main/java/org/transdroid/daemon/util/FakeSocketFactory.java
  7. 89
      app/src/main/java/org/transdroid/daemon/util/FakeTrustManager.java
  8. 17
      app/src/main/java/org/transdroid/daemon/util/HttpHelper.java
  9. 41
      app/src/main/java/org/transdroid/daemon/util/IgnoreSSLTrustManager.java
  10. 98
      app/src/main/java/org/transdroid/daemon/util/SelfSignedTrustManager.java
  11. 148
      app/src/main/java/org/transdroid/daemon/util/TlsSniSocketFactory.java
  12. 1
      app/src/main/res/values/changelog.xml

2
app/src/main/java/org/transdroid/core/seedbox/SeedstuffSettings.java

@ -54,7 +54,7 @@ public class SeedstuffSettings extends SeedboxSettingsImpl implements SeedboxSet @@ -54,7 +54,7 @@ public class SeedstuffSettings extends SeedboxSettingsImpl implements SeedboxSet
null,
443,
true,
true,
false,
null,
"/user/" + user,
true,

2
app/src/main/java/org/transdroid/core/seedbox/XirvikDediSettings.java

@ -55,7 +55,7 @@ public class XirvikDediSettings extends SeedboxSettingsImpl implements SeedboxSe @@ -55,7 +55,7 @@ public class XirvikDediSettings extends SeedboxSettingsImpl implements SeedboxSe
null,
type == Daemon.uTorrent? 5010: 443,
type == Daemon.uTorrent? false: true,
type == Daemon.uTorrent? false: true,
false,
null,
type == Daemon.Deluge? "/deluge": null,
true,

2
app/src/main/java/org/transdroid/core/seedbox/XirvikSemiSettings.java

@ -54,7 +54,7 @@ public class XirvikSemiSettings extends SeedboxSettingsImpl implements SeedboxSe @@ -54,7 +54,7 @@ public class XirvikSemiSettings extends SeedboxSettingsImpl implements SeedboxSe
null,
443,
true,
true,
false,
null,
"/RPC2",
true,

2
app/src/main/java/org/transdroid/core/seedbox/XirvikSharedSettings.java

@ -55,7 +55,7 @@ public class XirvikSharedSettings extends SeedboxSettingsImpl implements Seedbox @@ -55,7 +55,7 @@ public class XirvikSharedSettings extends SeedboxSettingsImpl implements Seedbox
null,
443,
true,
true,
false,
null,
rpc,
true,

20
app/src/main/java/org/transdroid/daemon/Vuze/VuzeXmlOverHttpClient.java

@ -47,7 +47,7 @@ import org.base64.android.Base64; @@ -47,7 +47,7 @@ import org.base64.android.Base64;
import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.util.FakeSocketFactory;
import org.transdroid.daemon.util.TlsSniSocketFactory;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
@ -114,12 +114,16 @@ public class VuzeXmlOverHttpClient { @@ -114,12 +114,16 @@ public class VuzeXmlOverHttpClient {
HttpConnectionParams.setSoTimeout(httpParams, settings.getTimeoutInMilliseconds());
SchemeRegistry registry = new SchemeRegistry();
SocketFactory httpsSocketFactory;
if (settings.getSslTrustKey() != null) {
httpsSocketFactory = new TlsSniSocketFactory(settings.getSslTrustKey());
} else if (settings.getSslTrustAll()) {
httpsSocketFactory = new TlsSniSocketFactory(true);
} else {
httpsSocketFactory = new TlsSniSocketFactory();
}
registry.register(new Scheme("http", new PlainSocketFactory(), 80));
SocketFactory https_socket =
settings.getSslTrustAll() ? new FakeSocketFactory()
: settings.getSslTrustKey() != null ? new FakeSocketFactory(settings.getSslTrustKey())
: SSLSocketFactory.getSocketFactory();
registry.register(new Scheme("https", https_socket, 443));
registry.register(new Scheme("https", httpsSocketFactory, 443));
client = new DefaultHttpClient(new ThreadSafeClientConnManager(httpParams, registry), httpParams);
if (settings.shouldUseAuthentication()) {
@ -128,7 +132,7 @@ public class VuzeXmlOverHttpClient { @@ -128,7 +132,7 @@ public class VuzeXmlOverHttpClient {
} else {
username = settings.getUsername();
password = settings.getPassword();
((DefaultHttpClient) client).getCredentialsProvider().setCredentials(
client.getCredentialsProvider().setCredentials(
new AuthScope(postMethod.getURI().getHost(), postMethod.getURI().getPort(), AuthScope.ANY_REALM),
new UsernamePasswordCredentials(username, password));
}
@ -141,7 +145,7 @@ public class VuzeXmlOverHttpClient { @@ -141,7 +145,7 @@ public class VuzeXmlOverHttpClient {
/**
* Convenience constructor. Creates new instance based on server String address
* @param settings The server connection settings
* @param uri The URI of the XML RPC to connect to
* @param url The URL of the XML RPC to connect to
* @throws DaemonException Thrown when settings are missing or conflicting
*/
public VuzeXmlOverHttpClient(DaemonSettings settings, String url) throws DaemonException {

86
app/src/main/java/org/transdroid/daemon/util/FakeSocketFactory.java

@ -1,86 +0,0 @@ @@ -1,86 +0,0 @@
package org.transdroid.daemon.util;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.scheme.LayeredSocketFactory;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
public class FakeSocketFactory implements SocketFactory, LayeredSocketFactory {
private String certKey = null;
private SSLContext sslcontext = null;
public FakeSocketFactory(String certKey){
this.certKey = certKey;
}
public FakeSocketFactory() { }
private static SSLContext createEasySSLContext(String certKey) throws IOException {
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[] { new FakeTrustManager(certKey) }, null);
return context;
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
private SSLContext getSSLContext() throws IOException {
if (this.sslcontext == null) {
this.sslcontext = createEasySSLContext(this.certKey);
}
return this.sslcontext;
}
@Override
public Socket connectSocket(Socket sock, String host, int port,
InetAddress localAddress, int localPort, HttpParams params) throws IOException,
UnknownHostException, ConnectTimeoutException {
int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
int soTimeout = HttpConnectionParams.getSoTimeout(params);
InetSocketAddress remoteAddress = new InetSocketAddress(host, port);
SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket());
if ((localAddress != null) || (localPort > 0)) {
// we need to bind explicitly
if (localPort < 0) {
localPort = 0; // indicates "any"
}
InetSocketAddress isa = new InetSocketAddress(localAddress,
localPort);
sslsock.bind(isa);
}
sslsock.connect(remoteAddress, connTimeout);
sslsock.setSoTimeout(soTimeout);
return sslsock;
}
@Override
public Socket createSocket() throws IOException {
return getSSLContext().getSocketFactory().createSocket();
}
@Override
public boolean isSecure(Socket arg0) throws IllegalArgumentException {
return true;
}
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose)
throws IOException, UnknownHostException {
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
}

89
app/src/main/java/org/transdroid/daemon/util/FakeTrustManager.java

@ -1,89 +0,0 @@ @@ -1,89 +0,0 @@
package org.transdroid.daemon.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class FakeTrustManager implements X509TrustManager {
private String certKey = null;
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[] {};
private static final String LOG_NAME = "TrustManager";
public FakeTrustManager(String certKey){
super();
this.certKey = certKey;
}
FakeTrustManager(){
super();
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
if( this.certKey == null ){
// This is the Accept All certificates case.
return;
}
// Otherwise, we have a certKey defined. We should now examine the one we got from the server.
// They match? All is good. They don't, throw an exception.
String our_key = this.certKey.replaceAll("[^a-fA-F0-9]+", "");
try {
//Assume self-signed root is okay?
X509Certificate ss_cert = chain[0];
String thumbprint = FakeTrustManager.getThumbPrint(ss_cert);
DLog.d(LOG_NAME, thumbprint);
if( our_key.equalsIgnoreCase(thumbprint) ){
return;
}
else {
throw new CertificateException("Certificate key [" + thumbprint + "] doesn't match expected value.");
}
} catch (NoSuchAlgorithmException e) {
throw new CertificateException("Unable to check self-signed cert, unknown algorithm. " + e.toString());
}
}
public boolean isClientTrusted(X509Certificate[] chain) {
return true;
}
public boolean isServerTrusted(X509Certificate[] chain) {
return true;
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return _AcceptedIssuers;
}
// Thank you: http://stackoverflow.com/questions/1270703/how-to-retrieve-compute-an-x509-certificates-thumbprint-in-java
private static String getThumbPrint(X509Certificate cert) throws NoSuchAlgorithmException, CertificateEncodingException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] der = cert.getEncoded();
md.update(der);
byte[] digest = md.digest();
return hexify(digest);
}
private static String hexify (byte bytes[]) {
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
StringBuffer buf = new StringBuffer(bytes.length * 2);
for (int i = 0; i < bytes.length; ++i) {
buf.append(hexDigits[(bytes[i] & 0xf0) >> 4]);
buf.append(hexDigits[bytes[i] & 0x0f]);
}
return buf.toString();
}
}

17
app/src/main/java/org/transdroid/daemon/util/HttpHelper.java

@ -41,7 +41,6 @@ import org.apache.http.conn.scheme.PlainSocketFactory; @@ -41,7 +41,6 @@ import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
@ -91,7 +90,7 @@ public class HttpHelper { @@ -91,7 +90,7 @@ public class HttpHelper {
* Creates a standard Apache HttpClient that is thread safe, supports different SSL auth methods and basic
* authentication
* @param sslTrustAll Whether to trust all SSL certificates
* @param sslTrustkey A specific SSL key to accept exclusively
* @param sslTrustKey A specific SSL key to accept exclusively
* @param timeout The connection timeout for all requests
* @param authAddress The authentication domain address
* @param authPort The authentication domain port number
@ -99,15 +98,21 @@ public class HttpHelper { @@ -99,15 +98,21 @@ public class HttpHelper {
* @throws DaemonException Thrown when information (such as username/password) is missing
*/
public static DefaultHttpClient createStandardHttpClient(boolean userBasicAuth, String username, String password,
boolean sslTrustAll, String sslTrustkey, int timeout, String authAddress, int authPort)
boolean sslTrustAll, String sslTrustKey, int timeout, String authAddress, int authPort)
throws DaemonException {
// Register http and https sockets
SchemeRegistry registry = new SchemeRegistry();
SocketFactory httpsSocketFactory;
if (sslTrustKey != null) {
httpsSocketFactory = new TlsSniSocketFactory(sslTrustKey);
} else if (sslTrustAll) {
httpsSocketFactory = new TlsSniSocketFactory(true);
} else {
httpsSocketFactory = new TlsSniSocketFactory();
}
registry.register(new Scheme("http", new PlainSocketFactory(), 80));
SocketFactory https_socket = sslTrustAll ? new FakeSocketFactory()
: sslTrustkey != null ? new FakeSocketFactory(sslTrustkey) : SSLSocketFactory.getSocketFactory();
registry.register(new Scheme("https", https_socket, 443));
registry.register(new Scheme("https", httpsSocketFactory, 443));
// Standard parameters
HttpParams httpparams = new BasicHttpParams();

41
app/src/main/java/org/transdroid/daemon/util/IgnoreSSLTrustManager.java

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
/*
* Copyright 2010-2013 Eric Kok et al.
*
* Transdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Transdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
*/
package org.transdroid.daemon.util;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class IgnoreSSLTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// Perform no check whatsoever on the validity of the SSL certificate
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// Perform no check whatsoever on the validity of the SSL certificate
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}

98
app/src/main/java/org/transdroid/daemon/util/SelfSignedTrustManager.java

@ -0,0 +1,98 @@ @@ -0,0 +1,98 @@
/*
* Copyright 2010-2013 Eric Kok et al.
*
* Transdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Transdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
*/
package org.transdroid.daemon.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class SelfSignedTrustManager implements X509TrustManager {
private static final X509Certificate[] acceptedIssuers = new X509Certificate[]{};
private static final String LOG_NAME = "TrustManager";
private String certKey = null;
public SelfSignedTrustManager(String certKey) {
super();
this.certKey = certKey;
}
// Thank you: http://stackoverflow.com/questions/1270703/how-to-retrieve-compute-an-x509-certificates-thumbprint-in-java
private static String getThumbPrint(X509Certificate cert)
throws NoSuchAlgorithmException, CertificateEncodingException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] der = cert.getEncoded();
md.update(der);
byte[] digest = md.digest();
return hexify(digest);
}
private static String hexify(byte bytes[]) {
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
StringBuffer buf = new StringBuffer(bytes.length * 2);
for (int i = 0; i < bytes.length; ++i) {
buf.append(hexDigits[(bytes[i] & 0xf0) >> 4]);
buf.append(hexDigits[bytes[i] & 0x0f]);
}
return buf.toString();
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
if (this.certKey == null) {
throw new CertificateException("Requires a non-null certificate key in SHA-1 format to match.");
}
// Qe have a certKey defined. We should now examine the one we got from the server.
// They match? All is good. They don't, throw an exception.
String ourKey = this.certKey.replaceAll("[^a-fA-F0-9]+", "");
try {
// Assume self-signed root is okay?
X509Certificate sslCert = chain[0];
String thumbprint = SelfSignedTrustManager.getThumbPrint(sslCert);
DLog.d(LOG_NAME, thumbprint);
if (ourKey.equalsIgnoreCase(thumbprint)) {
return;
} else {
CertificateException certificateException =
new CertificateException("Certificate key [" + thumbprint + "] doesn't match expected value.");
DLog.e(SelfSignedTrustManager.class.getSimpleName(), certificateException.toString());
throw certificateException;
}
} catch (NoSuchAlgorithmException e) {
throw new CertificateException("Unable to check self-signed cert, unknown algorithm. " + e.toString());
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return acceptedIssuers;
}
}

148
app/src/main/java/org/transdroid/daemon/util/TlsSniSocketFactory.java

@ -0,0 +1,148 @@ @@ -0,0 +1,148 @@
/*
* Copyright 2010-2013 Eric Kok et al.
*
* Transdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Transdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
*/
package org.transdroid.daemon.util;
import android.net.SSLCertificateSocketFactory;
import android.os.Build;
import org.apache.http.conn.scheme.LayeredSocketFactory;
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.apache.http.params.HttpParams;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
/**
* Implements an HttpClient socket factory with extensive support for SSL. Many thanks to
* http://blog.dev001.net/post/67082904181/android-using-sni-and-tlsv1-2-with-apache-httpclient for the base
* implementation.
* <p/>
* Firstly, all SSL protocols that a particular Android version support will be enabled (according to
* http://developer.android.com/reference/javax/net/ssl/SSLSocket.html). This currently includes SSL v3 and TLSv1.0,
* v1.1 and v1.2.
* <p/>
* Second, SNI is supported for host name verification. For Android 4.2+, which supports it natively, the default
* (strict) hostname verifier is used. For Android 4.1 and earlier it is possibly supported through reflexion on the
* same methods.
* <p/>
* Third, self-signed certificates are supported through the checking of the received certificate key with a given SHA-1
* encoded hex of the self-signed certificate key. When a key is given but not a correct match, the thumbprint of the
* server certificate is given, such that the correct SHA-1 hash to use can be foudn in the log.
* <p/>
* Finally, the ignoring of all SSL certificates (and hostname) is possible (which is obviously very insecure!).
*/
public class TlsSniSocketFactory implements LayeredSocketFactory {
private final static HostnameVerifier hostnameVerifier = new StrictHostnameVerifier();
private final boolean acceptAllCertificates;
private final String selfSignedCertificateKey;
public TlsSniSocketFactory() {
this.acceptAllCertificates = false;
this.selfSignedCertificateKey = null;
}
public TlsSniSocketFactory(String certKey) {
this.acceptAllCertificates = false;
this.selfSignedCertificateKey = certKey;
}
public TlsSniSocketFactory(boolean acceptAllCertificates) {
this.acceptAllCertificates = acceptAllCertificates;
this.selfSignedCertificateKey = null;
}
// Plain TCP/IP (layer below TLS)
@Override
public Socket connectSocket(Socket s, String host, int port, InetAddress localAddress, int localPort,
HttpParams params) throws IOException {
return null;
}
@Override
public Socket createSocket() throws IOException {
return null;
}
@Override
public boolean isSecure(Socket s) throws IllegalArgumentException {
if (s instanceof SSLSocket) {
return s.isConnected();
}
return false;
}
// TLS layer
@Override
public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException {
if (autoClose) {
// we don't need the plainSocket
plainSocket.close();
}
SSLCertificateSocketFactory sslSocketFactory =
(SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);
// For self-signed certificates use a custom trust manager
if (acceptAllCertificates) {
sslSocketFactory.setTrustManagers(new TrustManager[]{new IgnoreSSLTrustManager()});
} else if (selfSignedCertificateKey != null) {
sslSocketFactory.setTrustManagers(new TrustManager[]{new SelfSignedTrustManager(selfSignedCertificateKey)});
}
// create and connect SSL socket, but don't do hostname/certificate verification yet
SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(InetAddress.getByName(host), port);
// enable TLSv1.1/1.2 if available
ssl.setEnabledProtocols(ssl.getSupportedProtocols());
// set up SNI before the handshake
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
sslSocketFactory.setHostname(ssl, host);
} else {
try {
java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
setHostnameMethod.invoke(ssl, host);
} catch (Exception e) {
DLog.d(TlsSniSocketFactory.class.getSimpleName(), "SNI not usable: " + e);
}
}
// verify hostname and certificate
SSLSession session = ssl.getSession();
if (!(acceptAllCertificates || selfSignedCertificateKey != null) && !hostnameVerifier.verify(host, session)) {
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + host);
}
DLog.d(TlsSniSocketFactory.class.getSimpleName(),
"Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
" using " + session.getCipherSuite());
return ssl;
}
}

1
app/src/main/res/values/changelog.xml

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
<string name="system_changelog">
Transdroid 2.3.0\n
- Aria2 support\n
- Improved SSL support with TLS 1.1/1.2 and SNI hostname verification\n
- Fixed server checker when one is unavailable\n
- Fixed Deluge magnet links from Chrome\n
- Enable RSS notifications per feed\n

Loading…
Cancel
Save