/* * Overchan Android (Meta Imageboard Client) * Copyright (C) 2014-2016 miku-nyan <https://github.com/miku-nyan> * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package nya.miku.wishmaster.http.client; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.util.ArrayList; import java.util.List; import javax.net.SocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.X509TrustManager; import cz.msebera.android.httpclient.HttpHost; import cz.msebera.android.httpclient.conn.socket.LayeredConnectionSocketFactory; import cz.msebera.android.httpclient.conn.ssl.BrowserCompatHostnameVerifier; import cz.msebera.android.httpclient.conn.ssl.SSLContexts; import cz.msebera.android.httpclient.conn.ssl.X509HostnameVerifier; import cz.msebera.android.httpclient.protocol.HttpContext; import cz.msebera.android.httpclient.util.Args; import nya.miku.wishmaster.common.Logger; public class ExtendedSSLSocketFactory implements LayeredConnectionSocketFactory { private static final String TAG = "ExtendedSSLSocketFactory"; private static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER = new BrowserCompatHostnameVerifier(); private static volatile boolean initialized = false; private static SSLContext interactiveContext = null; private static ExtendedTrustManager interactiveTrustManager = null; private static X509HostnameVerifier interactiveHostnameVerifier = null; private static void initInteractiveObjects() throws Exception { if (initialized) return; synchronized (ExtendedSSLSocketFactory.class) { if (initialized) return; SSLContext context = SSLContexts.createDefault(); ExtendedTrustManager mtm = new ExtendedTrustManager(); context.init(null, new X509TrustManager[] { mtm }, null); X509HostnameVerifier verifier = mtm.wrapHostnameVerifier(BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); interactiveContext = context; interactiveTrustManager = mtm; interactiveHostnameVerifier = verifier; initialized = true; } } public static ExtendedSSLSocketFactory getSocketFactory() { try { initInteractiveObjects(); return new ExtendedSSLSocketFactory(interactiveContext, interactiveHostnameVerifier); } catch (Exception e) { Logger.e(TAG, "cannot instantiate interactive SSL socket factory", e); return new ExtendedSSLSocketFactory(SSLContexts.createDefault(), BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); } } public static ExtendedTrustManager getTrustManager() throws Exception { initInteractiveObjects(); return interactiveTrustManager; } private final javax.net.ssl.SSLSocketFactory socketfactory; private final X509HostnameVerifier hostnameVerifier; public ExtendedSSLSocketFactory(final SSLContext sslContext) { this(sslContext, null); } public ExtendedSSLSocketFactory(final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) { this(Args.notNull(sslContext, "SSL context").getSocketFactory(), hostnameVerifier); } public ExtendedSSLSocketFactory(final javax.net.ssl.SSLSocketFactory socketfactory, final X509HostnameVerifier hostnameVerifier) { this.socketfactory = Args.notNull(socketfactory, "SSL socket factory"); this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier : BROWSER_COMPATIBLE_HOSTNAME_VERIFIER; } public Socket createSocket(final HttpContext context) throws IOException { return SocketFactory.getDefault().createSocket(); } public Socket connectSocket( final int connectTimeout, final Socket socket, final HttpHost host, final InetSocketAddress remoteAddress, final InetSocketAddress localAddress, final HttpContext context) throws IOException { Args.notNull(host, "HTTP host"); Args.notNull(remoteAddress, "Remote address"); final Socket sock = socket != null ? socket : createSocket(context); if (localAddress != null) { sock.bind(localAddress); } try { if (connectTimeout > 0 && sock.getSoTimeout() == 0) { sock.setSoTimeout(connectTimeout); } sock.connect(remoteAddress, connectTimeout); } catch (final IOException ex) { try { sock.close(); } catch (final IOException ignore) { } throw ex; } // Setup SSL layering if necessary if (sock instanceof SSLSocket) { final SSLSocket sslsock = (SSLSocket) sock; sslsock.startHandshake(); verifyHostname(sslsock, host.getHostName()); return sock; } else { return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), context); } } public Socket createLayeredSocket( final Socket socket, final String target, final int port, final HttpContext context) throws IOException { final SSLSocket sslsock = (SSLSocket) this.socketfactory.createSocket( socket, target, port, true); // If supported protocols are not explicitly set, remove all SSL protocol versions final String[] allProtocols = sslsock.getSupportedProtocols(); final List<String> enabledProtocols = new ArrayList<String>(allProtocols.length); for (String protocol: allProtocols) { if (!protocol.startsWith("SSL")) { enabledProtocols.add(protocol); } } sslsock.setEnabledProtocols(enabledProtocols.toArray(new String[enabledProtocols.size()])); //prepareSocket(sslsock); // Android specific code to enable SNI try { sslsock.getClass().getMethod("setHostname", String.class).invoke(sslsock, target); } catch (NoSuchMethodException ex) { Logger.e(TAG, "SNI configuration failed: NoSuchMethodException"); } catch (Exception ex) { Logger.e(TAG, "SNI configuration failed", ex); } // End of Android specific code sslsock.startHandshake(); verifyHostname(sslsock, target); return sslsock; } X509HostnameVerifier getHostnameVerifier() { return this.hostnameVerifier; } private void verifyHostname(final SSLSocket sslsock, final String hostname) throws IOException { try { this.hostnameVerifier.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; } } }