package eu.nullbyte.android.urllib; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import android.os.Build; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import timber.log.Timber; public class CertPinningSSLSocketFactory extends SSLSocketFactory { private SSLContext sslcontext = null; private Certificate[] certificates; private String lastHost; private CertPinningTrustManager mTrustManager; private ClientCertificate mClientCertificate; public CertPinningSSLSocketFactory(ClientCertificate clientCertificate, Certificate[] certificates) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { super(null); this.certificates = certificates; this.mClientCertificate = clientCertificate; setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER); } private SSLContext createSSLContext() throws IOException { //Log.v(TAG, "createSSLContext()"); try { SSLContext context = SSLContext.getInstance("TLS"); mTrustManager = new CertPinningTrustManager(certificates, lastHost); KeyManager[] keyManagers = null; if (mClientCertificate != null) { KeyManagerFactory kmf = KeyManagerFactory.getInstance( KeyManagerFactory.getDefaultAlgorithm()); kmf.init(mClientCertificate.getKeyStore(), mClientCertificate.getPassword().toCharArray()); keyManagers = kmf.getKeyManagers(); } context.init(keyManagers, new TrustManager[]{mTrustManager}, null); return context; } catch (Exception e) { throw new IOException(e.getMessage(), e); } } // private static KeyManager2 extends KeyManager private SSLContext getSSLContext() throws IOException { //Log.v(TAG, "getSSLContext()"); if (this.sslcontext == null) { this.sslcontext = createSSLContext(); } return this.sslcontext; } /** * @see org.apache.http.conn.scheme.SocketFactory#connectSocket(java.net.Socket, * String, int, java.net.InetAddress, int, * org.apache.http.params.HttpParams) */ @Override public Socket connectSocket(Socket sock, String host, int port, InetAddress localAddress, int localPort, HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException { //Log.v(TAG, "connectSocket(socket: " + sock + ", host: " + host + ", port: " + port + ", localAddress: " + localAddress + ", localPort: " + localPort + ", params: " + params); 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); try { getHostnameVerifier().verify(host, sslsock); // verifyHostName() didn't blowup - good! } catch (IOException iox) { // close the socket before re-throwing the exception try { sslsock.close(); } catch (Exception e) { Timber.w(e, "Error closing SSL socket (ignored)"); } throw iox; } return sslsock; } /** * @see org.apache.http.conn.scheme.SocketFactory#createSocket() */ @Override public Socket createSocket() throws IOException { //Log.v(TAG, "createSocket()"); return secureSocket(getSSLContext().getSocketFactory().createSocket()); } /** * @see org.apache.http.conn.scheme.LayeredSocketFactory#createSocket(java.net.Socket, * String, int, boolean) */ @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { //Log.v(TAG, "createSocket(socket: " + socket + ", host: " + host + ", port: " + port + ", autoClose: " + autoClose); lastHost = host; return secureSocket( getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose)); } public void setHost(String host) { lastHost = host; if (mTrustManager != null) { mTrustManager.setHost(host); } } private Socket secureSocket(Socket socket) { if (!(socket instanceof SSLSocket)) { return socket; } SSLSocket vSocket = (SSLSocket) socket; // Remove SSLv3 support. // See https://code.google.com/p/android/issues/detail?id=78187 List<String> supportedProtocols = new ArrayList<String>( Arrays.asList(vSocket.getSupportedProtocols())); supportedProtocols.remove("SSLv3"); vSocket.setEnabledProtocols( supportedProtocols.toArray(new String[supportedProtocols.size()])); // Fix for supporting old servers. // See https://code.google.com/p/android-developer-preview/issues/detail?id=1200#c23 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { List<String> ciphers = new ArrayList<String>( Arrays.asList(vSocket.getEnabledCipherSuites())); ciphers.remove("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"); ciphers.remove("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"); vSocket.setEnabledCipherSuites(ciphers.toArray(new String[ciphers.size()])); } return vSocket; } }