Skip to content

Instantly share code, notes, and snippets.

@rayworks
Created July 21, 2020 10:39
Show Gist options
  • Save rayworks/91e954ce90ac7d07d07064736abc06a8 to your computer and use it in GitHub Desktop.
Save rayworks/91e954ce90ac7d07d07064736abc06a8 to your computer and use it in GitHub Desktop.
The compatible version of SSLSocketFactory for resolving SNI issue when using library android-async-http with version 1.4.x
import android.util.Log;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import cz.msebera.android.httpclient.conn.ssl.SSLSocketFactory;
import cz.msebera.android.httpclient.conn.ssl.X509HostnameVerifier;
import cz.msebera.android.httpclient.protocol.HttpContext;
/***
* The compatible version of {@link SSLSocketFactory} for resolving SNI issue.
* {@link cz.msebera.android.httpclient.conn.ssl.SSLConnectionSocketFactory} is the recommended one,
* however {@link com.loopj.android.http.AsyncHttpClient} doesn't integrate it properly.
*
* <p>
* TODO: refactor it with OKHttp
* @see <a href="https://github.com/smarek/httpclient-android/issues/7"> the original issue on httpclient-android</a>
* @see <a href="https://www.cloudflare.com/learning/ssl/what-is-sni/">What is SNI</a>
* </p>
*/
class CompatibleSSLSockFactory extends SSLSocketFactory {
public static final String TAG = "CompatSSLSockFactory";
private SSLContext sslContext;
public CompatibleSSLSockFactory(SSLContext sslContext, X509HostnameVerifier hostnameVerifier) {
super(sslContext, hostnameVerifier);
this.sslContext = sslContext;
}
@Override
public Socket createLayeredSocket(
final Socket socket,
final String target,
final int port,
final HttpContext context) throws IOException {
final SSLSocket sslsock = (SSLSocket) sslContext.getSocketFactory().createSocket(socket, target, port, true);
// internalPrepareSocket(sslsock);
// If supported protocols are not explicitly set, remove all SSL protocol versions
final String[] allProtocols = sslsock.getEnabledProtocols();
final List<String> enabledProtocols = new ArrayList<>(allProtocols.length);
for (String protocol : allProtocols) {
if (!protocol.startsWith("SSL")) {
enabledProtocols.add(protocol);
}
}
if (!enabledProtocols.isEmpty()) {
sslsock.setEnabledProtocols(enabledProtocols.toArray(new String[enabledProtocols.size()]));
}
// Android specific code to enable SNI : SDK 21+
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Enabling SNI for " + target);
}
try {
Method method = sslsock.getClass().getMethod("setHostname", String.class);
method.invoke(sslsock, target);
} catch (Exception ex) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "SNI configuration failed", ex);
}
}
// End of Android specific code
sslsock.startHandshake();
verifyHostname(sslsock, target);
return sslsock;
}
private void verifyHostname(final SSLSocket sslsock, final String hostname) throws IOException {
try {
BROWSER_COMPATIBLE_HOSTNAME_VERIFIER.verify(hostname, sslsock);
// verifyHostName() didn't blowup - good!
} catch (final IOException iox) {
// close the socket before re-throwing the exception
try {
sslsock.close();
} catch (final Exception x) { /*ignore*/ }
throw iox;
}
}
}
@rayworks
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment