mirror of
https://github.com/etesync/android
synced 2024-11-26 09:58:11 +00:00
Support HTTP(S) proxies (closes #232)
This commit is contained in:
parent
f69f449b44
commit
624f33c746
Binary file not shown.
Binary file not shown.
@ -34,59 +34,95 @@ public class TlsSniSocketFactory implements LayeredConnectionSocketFactory {
|
|||||||
|
|
||||||
final static TlsSniSocketFactory INSTANCE = new TlsSniSocketFactory();
|
final static TlsSniSocketFactory INSTANCE = new TlsSniSocketFactory();
|
||||||
|
|
||||||
private final static SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory)SSLCertificateSocketFactory.getDefault(0);
|
private final static SSLCertificateSocketFactory sslSocketFactory =
|
||||||
|
(SSLCertificateSocketFactory)SSLCertificateSocketFactory.getDefault(0);
|
||||||
private final static HostnameVerifier hostnameVerifier = new BrowserCompatHostnameVerifier();
|
private final static HostnameVerifier hostnameVerifier = new BrowserCompatHostnameVerifier();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
For SSL connections without HTTP(S) proxy:
|
||||||
|
1) createSocket() is called
|
||||||
|
2) connectSocket() is called which creates a new SSL connection
|
||||||
|
2a) SNI is set up, and then
|
||||||
|
2b) the connection is established, hands are shaken and certificate/host name are verified
|
||||||
|
|
||||||
|
Layered sockets are used with HTTP(S) proxies:
|
||||||
|
1) a new plain socket is created by the HTTP library
|
||||||
|
2) the plain socket is connected to http://proxy:8080
|
||||||
|
3) a CONNECT request is sent to the proxy and the response is parsed
|
||||||
|
4) now, createLayeredSocket() is called which wraps an SSL socket around the proxy connection,
|
||||||
|
doing all the set-up and verfication
|
||||||
|
4a) Because SSLSocket.createSocket(socket, ...) always does a handshake without allowing
|
||||||
|
to set up SNI before, *** SNI is not available for layered connections *** (unless
|
||||||
|
active by Android's defaults, which it isn't at the moment).
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Socket createSocket(HttpContext context) throws IOException {
|
public Socket createSocket(HttpContext context) throws IOException {
|
||||||
return sslSocketFactory.createSocket();
|
return new Socket();
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
|
|
||||||
@Override
|
@Override
|
||||||
public Socket connectSocket(int timeout, Socket socket, HttpHost host, InetSocketAddress remoteAddr, InetSocketAddress localAddr, HttpContext context) throws IOException {
|
public Socket connectSocket(int timeout, Socket plain, HttpHost host, InetSocketAddress remoteAddr, InetSocketAddress localAddr, HttpContext context) throws IOException {
|
||||||
// we'll rather create a new socket
|
Log.d(TAG, "Preparing direct SSL connection (without proxy) to " + host);
|
||||||
socket.close();
|
|
||||||
|
|
||||||
// create and connect SSL socket, but don't do hostname/certificate verification yet
|
// we'll rather use an SSLSocket directly
|
||||||
|
plain.close();
|
||||||
|
|
||||||
|
// create a plain SSL socket, but don't do hostname/certificate verification yet
|
||||||
SSLSocket ssl = (SSLSocket)sslSocketFactory.createSocket(remoteAddr.getAddress(), host.getPort());
|
SSLSocket ssl = (SSLSocket)sslSocketFactory.createSocket(remoteAddr.getAddress(), host.getPort());
|
||||||
|
|
||||||
// set reasonable SSL/TLS settings before the handshake:
|
// connect, set SNI, shake hands, verify, print connection info
|
||||||
// - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <4.4.3, if available)
|
connectWithSNI(ssl, host.getHostName());
|
||||||
ssl.setEnabledProtocols(ssl.getSupportedProtocols());
|
|
||||||
|
|
||||||
// - set SNI host name
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
|
||||||
sslSocketFactory.setHostname(ssl, host.getHostName());
|
|
||||||
// TODO sslSocketFactory.setUseSessionTickets(ssl, true);
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection");
|
|
||||||
try {
|
|
||||||
java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
|
|
||||||
setHostnameMethod.invoke(ssl, host.getHostName());
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, "SNI not useable", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify hostname and certificate
|
|
||||||
SSLSession session = ssl.getSession();
|
|
||||||
if (!hostnameVerifier.verify(host.getHostName(), session))
|
|
||||||
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + host);
|
|
||||||
|
|
||||||
Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
|
|
||||||
" using " + session.getCipherSuite());
|
|
||||||
|
|
||||||
return ssl;
|
return ssl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TLS layer
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Socket createLayeredSocket(Socket plainSocket, String host, int port, HttpContext context) throws IOException, UnknownHostException {
|
public Socket createLayeredSocket(Socket plain, String host, int port, HttpContext context) throws IOException, UnknownHostException {
|
||||||
Log.wtf(TAG, "createLayeredSocket should never be called");
|
Log.d(TAG, "Preparing layered SSL connection (over proxy) to " + host);
|
||||||
return plainSocket;
|
|
||||||
|
// create a layered SSL socket, but don't do hostname/certificate verification yet
|
||||||
|
SSLSocket ssl = (SSLSocket)sslSocketFactory.createSocket(plain, host, port, true);
|
||||||
|
|
||||||
|
// already connected, but verify host name again and print some connection info
|
||||||
|
connectWithSNI(ssl, host);
|
||||||
|
|
||||||
|
return ssl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
|
||||||
|
private void connectWithSNI(SSLSocket ssl, String host) throws SSLPeerUnverifiedException {
|
||||||
|
if (!ssl.isConnected()) {
|
||||||
|
// set reasonable SSL/TLS settings before the handshake:
|
||||||
|
// - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <4.4.3, if available)
|
||||||
|
ssl.setEnabledProtocols(ssl.getSupportedProtocols());
|
||||||
|
|
||||||
|
// - set SNI host name
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
|
Log.d(TAG, "Using documented SNI with host name " + host);
|
||||||
|
sslSocketFactory.setHostname(ssl, host);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection");
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
|
||||||
|
setHostnameMethod.invoke(ssl, host);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, "SNI not useable", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
Log.d(TAG, "Socket is already connected, SNI/TLv1.2 not available unless activated by Android defaults");
|
||||||
|
|
||||||
|
// verify hostname and certificate
|
||||||
|
SSLSession session = ssl.getSession();
|
||||||
|
if (!hostnameVerifier.verify(host, session))
|
||||||
|
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + host);
|
||||||
|
|
||||||
|
Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
|
||||||
|
" using " + session.getCipherSuite());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user