0

I know this has been asked many times and I've read a lot of solutions, but I'm having trouble making any of them to work. So my main problem is that I need to use a self signed certificate to connect to my server - I'm using tomcat and I already configured it to work with the JKS (I generated a .pem files with openssl and transformed them into JKS as explained here: Tomcat HTTPS keystore certificate) From my browser everything works fine, but now I need my app to connect via ssl. When I couldn't make anything work I tried to allow all certificates like this:

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;

import javax.net.ssl.*;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    new Thread() {
        public void run() {
            try {
                KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
                trustStore.load(null, null);

                SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
                sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

                HttpParams params = new BasicHttpParams();
                HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
                HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

                SchemeRegistry registry = new SchemeRegistry();
                registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
                registry.register(new Scheme("https", sf, 443));

                ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);


                HttpGet httpGet = new HttpGet("https://MY_IP:8443");

                HttpClient client = new DefaultHttpClient(ccm, params);
                HttpResponse res = client.execute(httpGet);
                Log.i("client", res.getStatusLine().toString());


            } catch (Exception e) {
                Log.e("client", "problem with connection");
            }
        }
    }.start();
}

public class MySSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");

    public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);

        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };

        sslContext.init(null, new TrustManager[]{tm}, null);
    }
}

}

And I get this error:

Catch exception while startHandshake: javax.net.ssl.SSLHandshakeException:     java.security.cert.CertPathValidatorException: Trust anchor for certification path not

return an invalid session with invalid cipher suite of SSL_NULL_WITH_NULL_NULL

with http connection it works :

HttpGet httpGet = new HttpGet("http://MY_IP:8080");

My questions are:

  1. how can I make it work by accepting all certificates?
  2. how do I define it to accept only my cert? (and that it will match the JKS I have in my tomcat server)
Community
  • 1
  • 1
user_s
  • 1,058
  • 2
  • 12
  • 35

2 Answers2

1

Ok so I finally managed to make it work! This is what iv'e done:

  1. Create keystore file with the java key tool:

    keytool -genkey -alias tomcat -keyalg RSA
    

    type your host name when asked for first and last name(so if you use tomcat with localhost enter your ip). this will create .keystore file in your home dir.

  2. Move the .ketstore file to %CATALINA_HOME%/conf and add to server.xml file the following:

     <Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
      SSLEnabled="true" maxThreads="150" scheme="https" secure="true"
      clientAuth="false" sslProtocol="TLS" keystoreFile="*%CATALINA_HOME%*\conf\.keystore" 
      keystorePass="*yourpass*"/>
    
  3. Now for the client app you'll have to use bouncy castle because android has a built-in support for the .bks file it creates. It can be downloaded from here: http://www.bouncycastle.org/latest_releases.html you'll also need openssl to create the certificate bouncy castle needs. open console and type :

    openssl s_client -connect *yourhostname*:8443/>cert.pem
    

    when done open the cert.pem file and delete everything before BEGIN CERTIFICATE and everything after END CERTIFICATE.

  4. to create to .bks file, type in console :

     keytool -import -alias tomcat -file *pathtToCertificate*\cert.pem -keypass *yourPassword* -keystore *pathtToSaveBks*\*nameOfYourKey*.bks -storetype BKS -storepass *yourPassword* -providerClass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath *fullPathToTheBouncyCastleJar*
    

    put the .bks file in your appproject\res\raw folder (create it if dosn't exist).

  5. this is a basic app that creates a connection via https(credit to Vipul from here: How to create a BKS (BouncyCastle) format Java Keystore that contains a client certificate chain):

    import java.io.InputStream;
    import java.security.KeyStore;
    import org.apache.http.conn.ClientConnectionManager;
    import org.apache.http.conn.scheme.PlainSocketFactory;
    import org.apache.http.conn.scheme.Scheme;
    import org.apache.http.conn.scheme.SchemeRegistry;
    import org.apache.http.conn.ssl.SSLSocketFactory;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.impl.conn.SingleClientConnManager;
    import com.arisglobal.aglite.activity.R;
    import android.content.Context;
    
    public class MyHttpClient extends DefaultHttpClient{
    final Context context;
    
    public MyHttpClient(Context context) {
        this.context = context;
    }
    
    @Override
    protected ClientConnectionManager createClientConnectionManager() {
        SchemeRegistry registry = new SchemeRegistry();
    
        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
    
        // Register for port 443 our SSLSocketFactory with our keystore to the ConnectionManager
        registry.register(new Scheme("https", newSslSocketFactory(), 443));
        return new SingleClientConnManager(getParams(), registry);
    }
    
    private SSLSocketFactory newSslSocketFactory() {
        try {
            // Get an instance of the Bouncy Castle KeyStore format
            KeyStore trusted = KeyStore.getInstance("BKS");
    
            // Get the raw resource, which contains the keystore with your trusted certificates (root and any intermediate certs)
            InputStream in = context.getResources().openRawResource(R.raw.*nameOfYourKey*);
            try {
                // Initialize the keystore with the provided trusted certificates.
                // Also provide the password of the keystore
                trusted.load(in, *yourPassword*.toCharArray());
            } finally {
                in.close();
            }
    
            // Pass the keystore to the SSLSocketFactory. The factory is responsible for the verification of the server certificate.
            SSLSocketFactory sf = new SSLSocketFactory(trusted);
    
            // Hostname verification from certificate
            // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
            sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
            return sf;
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }
    }
    

    and in your main activity create new thread (network operations cannot be done on the main thread)that it's run method does:

    DefaultHttpClient client = new MyHttpClient(getApplicationContext());
    HttpResponse response = client.execute(https://*yourIP|HostName:8443);
    

    and start it. if you encounter problems with getApplicationContext() it's because you are calling it in the wrong place. add Context context to your function signature and use it instead of getApplicationContext()

Community
  • 1
  • 1
user_s
  • 1,058
  • 2
  • 12
  • 35
0

Blindly trusting all certificates is not a good idea.

To trust your own self-signed cert, you need to have the server's certificate in the android application's trust store.

The code is too long to post here, so take a look at the blog post I wrote on this subject a couple of years ago: http://chariotsolutions.com/blog/post/https-with-client-certificates-on

and the source code at: https://github.com/rfreedman/android-ssl

GreyBeardedGeek
  • 29,460
  • 2
  • 47
  • 67