1

In our app when the app communicate with the server I made additional check of certificates (public key pinning) (against MITMA). To communicate with the server I use HttpClient. Also I have some proxy server in production, so I need to use SNI. Before we publish the app to production we check it in another environment (TEST env). For TEST env we have only self-signed certificate, cause its using for testing only and we don’t want to buy a new certificate just for this case.

To implement it I created the custom SSLSocketFactory (org.apache.http.conn.ssl.SSLSocketFactory). But the problem is it doesn't work for self-signed certificates. I set the custom trustManager (IgnoreCertificatesTrustManager) to sslSocketFactory (SSLCertificateSocketFactory). If I use the following code:

SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS); sslContext.init(null, new TrustManager[] { new IgnoreCertificatesTrustManager() }, null); sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); 

the check of self-singed certificates works (ignore the check in TrustManager (IgnoreCertificatesTrustManager)). But the code doesn't support the SNI solution. What did I do wrong?

Thanks.

private class CustomSSLSocketFactory extends SSLSocketFactory { public CustomSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException, CertificateException { super(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 ((SSLSocket) s).isConnected(); } return false; } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { SSLSocket sslSocket = null; // if (isProduction()) { if (autoClose) { // we don't need the plainSocket socket.close(); } // create and connect SSL socket, but don't do hostname/certificate verification yet SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0, null); // NOT works! sslSocketFactory.setTrustManagers(new TrustManager[] { new IgnoreCertificatesTrustManager() }); // ---- sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, host, port, autoClose); // enable TLSv1.1/1.2 if available (see https://github.com/rfc2822/davdroid/issues/229 ) sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols()); // set up SNI before the handshake if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { logger.debug("Setting SNI hostname"); sslSocketFactory.setHostname(sslSocket, host); } else { logger.debug("No documented SNI support on Android <4.2, trying with reflection"); try { java.lang.reflect.Method setHostnameMethod = sslSocket.getClass().getMethod("setHostname", String.class); setHostnameMethod.invoke(sslSocket, host); } catch (Exception e) { logger.error("SNI not useable", e); } } // } else { // try { // SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS); // sslContext.init(null, new TrustManager[] { new IgnoreCertificatesTrustManager() }, null); // sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); // } catch (java.security.NoSuchAlgorithmException e) { // throw new IOException(e); // } catch (KeyManagementException e) { // throw new IOException(e); // } // } // verify certificate SSLSession session = sslSocket.getSession(); X509Certificate[] certificates = (X509Certificate[]) session.getPeerCertificates(); if (!checkPublicKey(certificates)) { throw new IOException("SSL_HANDSHAKE_FAILED"); } return sslSocket; } } 
5
  • 1
    You don't need any of this. Just initialize an SSLContext with your trust manager. Strange definition of isSecure(). What does isConnected() have to do with it? Commented Jan 11, 2015 at 11:31
  • Are you sure that you use a version of HTTPClient which supports SNI? Usually Android ships with an old version of Apache HTTPClient which does not support SNI. Commented Jan 11, 2015 at 12:10
  • If you want to support both self-signed certs and regular CA-rooted certs from the same SSLContext, you will need to craft a suitable TrustManager that can do the boolean logic. See my TrustManagerBuilder in my CWAC-Security library. Commented Jan 11, 2015 at 12:32
  • Thanks all for the quick responses. Commented Jan 12, 2015 at 7:31
  • EJP I need SNI to check public key of certificates. Also isConnected() I think you are right. It is my mistake. It should be return true. I will check it. Steffen Ullrich I found that httpClient supports it from Android API 17. Since Android 2.3, SNI is available in the OpenSSL implementation, so I can use it via reflection. CommonsWare I would not want to add check of self-signed certificates (in our department we haven't access to them, i can't get keystore). I want to ignore them for NON-prod environment Commented Jan 12, 2015 at 7:46

1 Answer 1

2

I found the problem. It's my fault. Instead of line: sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, host, port, autoClose); You should use: sslSocket = (SSLSocket) sslSocketFactory.createSocket(InetAddress.getByName(host), port); Why? Because Hostname verification is not performed with the method sslSocketFactory.createSocket(InetAddress, int).

Also the method ‘isSecure’ also wrote right in the post, because we don’t know what port we are using, so if the instance of socket is ssl, check if it connected. if yes then isSecure should return true otherwise false.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.