-1

I'm trying to create an Android app that uses 2-way SSL to communicate with a NodeJS application. I have 2 versions of the code to make the request, and neither version is working. This first version of code works if I run it with plain Java, but when I try to pull this into my Android app, the server is not recognizing the client certificate:

Version 1:

    System.setProperty("javax.net.ssl.keyStore", "jks-keystore.jks");
    System.setProperty("javax.net.ssl.keyStorePassword", "pass1");
    System.setProperty("javax.net.ssl.trustStore", "jkstruststore.jks");
    System.setProperty("javax.net.ssl.trustStorePassword", "pass2");



    // specify url
    String url = "https://example.com/startup";

    System.out.println("Startup URL is " + url);

    // This block of code keeps self-signed certificates from causing errors.
    javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
        new javax.net.ssl.HostnameVerifier(){
            public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) {
                return true;
            }
        }
    );

    // initiate the request
    try
    {
        URL hp = new URL(url);
        HttpsURLConnection hpCon = (HttpsURLConnection)hp.openConnection();

        boolean isProxy = hpCon.usingProxy();
        InputStream obj = (InputStream) hpCon.getInputStream();

        // print out JSON response
        System.out.println(convertStreamToString(obj));
    }
    catch (Exception ex)
    {
        ex.printStackTrace();
    }

Error 1:

03-17 12:25:18.616: W/System.err(331): javax.net.ssl.SSLHandshakeException:     
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

Version 2:

          // load truststore certificate
          InputStream clientTruststoreIs = getResources().openRawResource(R.raw.tsserver);
          KeyStore trustStore = null;
          trustStore = KeyStore.getInstance("BKS");
          trustStore.load(clientTruststoreIs, "pass1".toCharArray());

          System.out.println("Loaded server certificates: " + trustStore.size());

          // initialize trust manager factory with the read truststore
          TrustManagerFactory trustManagerFactory = null;
          trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
          trustManagerFactory.init(trustStore);

          // setup client certificate

          // load client certificate
          InputStream keyStoreStream = getResources().openRawResource(R.raw.tsclient);
          KeyStore keyStore = null;
          keyStore = KeyStore.getInstance("BKS");
          keyStore.load(keyStoreStream, "pass2".toCharArray());

          System.out.println("Loaded client certificates: " + keyStore.size());

          // initialize key manager factory with the read client certificate
          KeyManagerFactory keyManagerFactory = null;
          keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
          keyManagerFactory.init(keyStore, "pass2".toCharArray());


          // initialize SSLSocketFactory to use the certificates
          SSLSocketFactory socketFactory = new SSLSocketFactory(SSLSocketFactory.TLS, keyStore, "pass2", trustStore, null, null);

          // Set basic data
          HttpParams params = new BasicHttpParams();
          HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
          HttpProtocolParams.setContentCharset(params, "UTF-8");
          HttpProtocolParams.setUseExpectContinue(params, true);
          HttpProtocolParams.setUserAgent(params, "Android app/1.0.0");

          // Make pool
          ConnPerRoute connPerRoute = new ConnPerRouteBean(12);
          ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
          ConnManagerParams.setMaxTotalConnections(params, 20);

          // Set timeout
          HttpConnectionParams.setStaleCheckingEnabled(params, false);
          HttpConnectionParams.setConnectionTimeout(params, 20 * 1000);
          HttpConnectionParams.setSoTimeout(params, 20 * 1000);
          HttpConnectionParams.setSocketBufferSize(params, 8192);

          // Some client params
          HttpClientParams.setRedirecting(params, false);

          // Register http/s schemas!
          SchemeRegistry schReg = new SchemeRegistry();
          schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
          schReg.register(new Scheme("https", socketFactory, 443));
          ClientConnectionManager conMgr = new ThreadSafeClientConnManager(params, schReg);
          DefaultHttpClient sClient = new DefaultHttpClient(conMgr, params);

          try {
                String res = executeHttpGet(sClient, "https://example.com/startup");
                System.out.println("------- SSL RESULT IS = " + res);
            } catch (Exception e) {
                System.out.println("---- ex " + e.getMessage());
                e.printStackTrace();
            }

Error 2: Server does not see client certificate

Both of these code samples result in the server not seeing the client certificate. Any ideas why this is not working? Thank you.

GingerHead
  • 8,130
  • 15
  • 59
  • 93
Seth
  • 1,353
  • 1
  • 8
  • 17
  • 2
    https://developer.android.com/training/articles/security-ssl.html#CommonProblems – vzamanillo Mar 18 '15 at 14:32
  • 2
    @vzamanillo The link you sent me says the error #1 that I get normally happens when you are not trusting the server certificate. But my code already adds the trusted certificate into the trust store, so shouldn't this part be working as it is written not? Thank you. – Seth Mar 18 '15 at 18:18
  • 2
    The problem seems to be the certificate itself. – vzamanillo Mar 18 '15 at 20:23
  • 2
    @vzamanillo I have tested the script in Node using the same certificates and it works fine. I can also run test #2 in plain Java (not Android) and it also works. For some reason, trying to do the same thing in the Android project isn't working. – Seth Mar 18 '15 at 20:33
  • 4
    The documentation says that self-signed certificates also throws that error. It's not important if they are trusted: if you use a self signed cert for the server, it will throw a SSLHandshakeException. I suggest to create your own CA and add that CA to your truststore. Then all certificates emitted from that CA will be trusted, without the need to add them directly inside da truststore file. – Giuseppe Bertone Mar 20 '15 at 15:26
  • 2
    @GiuseppeBertone I thought having a trust store was supposed to bypass the self-signed certificate error. Is that incorrect? Could you please provide a link or example for how to add a CA to the truststore? Thank you! – Seth Mar 23 '15 at 13:55
  • Are you creating these SSL certs dynamically so there is a different one for each connection? Or are you just using one self signed cert on your server? I'd just spring for the few bucks and get a real ssl cert that will be trusted without having to build a workaround. Plus when you make an iOS version of your app, you will have this same battle again. – greg_diesel Mar 27 '15 at 14:48

1 Answers1

0

If we read closely the documentaion we can find out that:

Self-signed certificates throw errors similar to that you have been experiencing. It's not important for self-signed certificates to be trusted to pass without any errors for the server, it will throw a SSLHandshakeException in either case.

To walk-pass this is to assemble a group of trusted CA certificates and add them to your truststore. Then all certificates emitted from that CA will be trusted, which will neglect the need to add them directly inside da truststore file.

Now how to add CA trusted certificates to the truststore:

Set up your own CA here

then:

Given a CA certificate, cacert.pem, in PEM format, you can add the certificate to a JKS truststore (or create a new truststore) by entering the following command:

keytool -import -file cacert.pem -alias CAAlias -keystore truststore.ts -storepass StorePass 

Where CAAlias is a convenient tag that enables you to access this particular CA certificate using the keytool utility. The file, truststore.ts, is a keystore file containing CA certificates—if this file does not already exist, the keytool utility creates one. The StorePass password provides access to the keystore file, truststore.t

Look at to this document.

  • 2
    Thanks! I haven't gotten a chance to test but I wanted to give you the bounty. The bounty already expired so I gotta try to figure out how to give that to you :/ – Seth Mar 28 '15 at 22:16
  • 2
    After I verify this works I will start a new bounty and assign to you. – Seth Mar 28 '15 at 22:20
  • I'm still getting the same error. I think I am not setting the keystores up correctly. My understanding is the trust store should ONLY contain the CA, is that right? And the client keystore should contain the client key and cert? Assuming that is correct, the part I am missing is how to correctly make the request in code using the keystores and/or certs directly. Thank you very much. If you can help me get this working, there will be 100 bounty in it for you :) – Seth Mar 30 '15 at 19:53
  • You don't need any keystores only a truststore, please read the documents –  Mar 31 '15 at 09:56
  • Well, I have good news and bad news. The good news is I was able to get a working code sample from the link you provided where the CA is trusted. The bad news is the link you sent me is only for 1-way SSL, not 2-way. In order for this to work, I need the Android app to also send up a client certificate with the request. Any ideas how to do that now that I have the CA working? Thank you very much. – Seth Mar 31 '15 at 14:41
  • look at this http://stackoverflow.com/questions/12156404/android-why-client-not-sending-ssl-certificate-when-its-not-signed-by-the-same –  Mar 31 '15 at 14:58