SSL mutual authentication FAIL on Android Client accepts servers certificate but server does not get the client cert
Asked Answered
I

1

6

I am trying to set up a mutually authenticated SSL between a Linux Server and Android APP. So far, I have been able to get the app to work with the server certificate communicate via SSL but once I set the server to only accept client certificates it stops working. The server configuration seems ok, but I am a kind of stuck. My best guess is that the client certificate is not correctly being presented to the server but got no idea how to test it next. I tried using the .pem for the client in my OS X keychain but the browsers does not seem to work with that certificate. Then again the Server certificate works perfectly because I can achieve https connection and the APP Accepts my unsigned server certificate.

The code I have been I am using is the combination of various tutorials, answers this is the main ones I have bookmarked:

This are the two main classes I am using for the connection: 1) This class handles the JSON parsing and does the REQUESTS

package edu.hci.additional;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.List;

import android.content.Context;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;

public class JSONParser {

    static InputStream is = null;
    static JSONObject jObj = null;
    static String json = "";

    // constructor
    public JSONParser() {

    }

    // function get json from url
    // by making HTTP POST or GET mehtod
    public JSONObject makeHttpRequest(String url, String method,
            List<NameValuePair> params, Context context) {

        // Making HTTP request
        try {

            // check for request method
            if(method == "POST"){
                // request method is POST
                // defaultHttpClient
                SecureHttpClient httpClient = new SecureHttpClient(context);
                HttpPost httpPost = new HttpPost(url);
                httpPost.setEntity(new UrlEncodedFormEntity(params));

                HttpResponse httpResponse = httpClient.execute(httpPost);
                HttpEntity httpEntity = httpResponse.getEntity();
                is = httpEntity.getContent();

            }else if(method == "GET"){
                // request method is GET
                SecureHttpClient httpClient = new SecureHttpClient(context);
                String paramString = URLEncodedUtils.format(params, "utf-8");
                url += "?" + paramString;
                HttpGet httpGet = new HttpGet(url);

                HttpResponse httpResponse = httpClient.execute(httpGet);
                HttpEntity httpEntity = httpResponse.getEntity();
                is = httpEntity.getContent();
            }           


        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    is, "iso-8859-1"), 8);
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
            is.close();
            json = sb.toString();
        } catch (Exception e) {
            Log.e("Buffer Error", "Error converting result " + e.toString());
        }

        // try parse the string to a JSON object
        try {
            jObj = new JSONObject(json);
        } catch (JSONException e) {
            Log.e("JSON Parser", "Error parsing data " + e.toString());
        }

        // return JSON String
        return jObj;

    }
}

This second class handles the SSL Authentication:

package edu.hci.additional;

import android.content.Context;
import android.util.Log;
import edu.hci.R;
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.params.HttpParams;

import java.io.IOException;
import java.io.InputStream;
import java.security.*;


public class SecureHttpClient extends DefaultHttpClient {

    private static Context appContext = null;
    private static HttpParams params = null;
    private static SchemeRegistry schmReg = null;
    private static Scheme httpsScheme = null;
    private static Scheme httpScheme = null;
    private static String TAG = "MyHttpClient";

    public SecureHttpClient(Context myContext) {

        appContext = myContext;

        if (httpScheme == null || httpsScheme == null) {
            httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
            httpsScheme = new Scheme("https", mySSLSocketFactory(), 443);
        }

        getConnectionManager().getSchemeRegistry().register(httpScheme);
        getConnectionManager().getSchemeRegistry().register(httpsScheme);

    }

    private SSLSocketFactory mySSLSocketFactory() {
        SSLSocketFactory ret = null;
        try {

            final KeyStore clientCert = KeyStore.getInstance("BKS");
            final KeyStore serverCert = KeyStore.getInstance("BKS");

            final InputStream client_inputStream = appContext.getResources().openRawResource(R.raw.authclientcerts);
            final InputStream server_inputStream = appContext.getResources().openRawResource(R.raw.certs);

            clientCert.load(client_inputStream, appContext.getString(R.string.client_store_pass).toCharArray());
            serverCert.load(server_inputStream, appContext.getString(R.string.server_store_pass).toCharArray());

            String client_password = appContext.getString(R.string.client_store_pass);

            server_inputStream.close();
            client_inputStream.close();

            ret = new SSLSocketFactory(clientCert,client_password,serverCert);
        } catch (UnrecoverableKeyException ex) {
            Log.d(TAG, ex.getMessage());
        } catch (KeyStoreException ex) {
            Log.d(TAG, ex.getMessage());
        } catch (KeyManagementException ex) {
            Log.d(TAG, ex.getMessage());
        } catch (NoSuchAlgorithmException ex) {
            Log.d(TAG, ex.getMessage());
        } catch (IOException ex) {
            Log.d(TAG, ex.getMessage());
        } catch (Exception ex) {
            Log.d(TAG, ex.getMessage());
        } finally {
            return ret;
        }
    }

}

To Create the keys I used openssl with this command:

openssl req -nodes -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 500

To get the keys to BKS for Android I used the bouncy castle bcprov-jdk15on-150.jar located at: http://www.bouncycastle.org/latest_releases.html

And used the command:

keytool -import -v -trustcacerts -alias 0 -file ~/cert.pem -keystore ~/Downloads/authclientcerts.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath ~/Downloads/bcprov-jdk15on-150.jar -storepass passWORD

Finally the lines I added to /etc/httpd/conf.d/ssl.conf to require the client certificate and check for the certificate validity (which match the client certificate I created) in Fedora 19 are:

...
SSLVerifyClient require
SSLVerifyDepth  5
...
<Location />
SSLRequire (    %{SSL_CLIENT_S_DN_O} eq "Develop" \
            and %{SSL_CLIENT_S_DN_OU} in {"Staff", "Operations", "Dev"} )
</Location>
...
SSLOptions +FakeBasicAuth +StrictRequire

I tried a lot of combinations on this config file and all ended in the same result twrowing me a "SSLPeerUnverifiedException: No peer certificate" Exception. I comment this lines on the SSL config file of the server and all works great but the server accepts all clients which is not what I need.

Thanks in advance :)

UPDATE

@EJP's Answer did the trick

First I had to add the certificate to the correct ( /etc/pki/tls/certs/ )path and load it by using: rename the cert: mv ca-andr.pem ca-andr.crt And now load the certificate:

 ln -s ca-andr.crt $( openssl x509 -hash -noout -in ca-andr.crt )".0"

This will create a openSSL readable symlink with a name similar to “f3f24175.0”

Then I set the new Certificate file in the /etc/httpd/conf.d/ssl.conf Configuration file.

…
SSLCACertificateFile /etc/pki/tls/certs/f2f62175.0
…

Now restart the http service and test the if the certificate is loaded with:

openssl verify -CApath /etc/pki/tls/certs/ f2f62175.0

If all is ok you should see:

f3f24175.0: OK

And you can end the testing with:

openssl s_client -connect example.com:443 -CApath /etc/pki/tls/certs

This should return the list of trusted client certificates (If you see the one you added, is working)

Now the second part of the problem was that my authclientcerts.BKS din’t contain the private key, so the password I supplied was never used and the server would not authenticate the cert. So I exported my key and cert to pkcs12 and updated the JAVA code accordingly.

Export command:

openssl pkcs12 -export -in ~/cert.pem -inkey ~/key.pem > android_client_p12.p12

Then the I changed the parts of the SecureHttpClient.java Class to make the client certificate with PKCS12 instead of BKS.

To change the key store type from BKS to PKCS12 I Replaced:

final KeyStore clientCert = KeyStore.getInstance("BKS”);

For this:

final KeyStore clientCert = KeyStore.getInstance("PKCS12");

And then I updated the references to the actual key store files located on res/raw/ By Replacing:

final InputStream client_inputStream = appContext.getResources().openRawResource(R.raw.authclientcerts);

For this:

final InputStream client_inputStream = appContext.getResources().openRawResource(R.raw.android_client_p12);

And that did the trick :D

Inseparable answered 16/4, 2014 at 15:39 Comment(0)
C
4

When the server asks for the client certificate, it provides a list of CAs that it will accept certificates signed by. If the client doesn't have a certificate signed by one of them, it won't send a certificate in reply. If the server is configured to require a client certificate, as opposed to just wanting one, it will then close the connection.

So, ensure that the client has a certificate which is acceptable to the server's truststore.

Cytosine answered 16/4, 2014 at 22:26 Comment(1)
Thanks that answer cleared things for me. I was able to successfully connect the Android App with the server using Mutual Authentication. Thank you so much for taking time to answer. I am adding in a few minutes an update with the full solution and the things I had to correct add.Inseparable

© 2022 - 2024 — McMap. All rights reserved.