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
Chrome: Verifying that Your Client Certificate Is InstalledIn Chrome, go to Settings. On the Settings page, below Default browser, click Show advanced settings. Under HTTPS/SSL, click Manage certificates. In the Certificates window, on the Personal tab, you should see your Client Certificate.
SSL pinning allows the application to only trust the valid or pre-defined certificate or Public Key. The application developer uses SSL pinning technique as an additional security layer for application traffic. As normally, application trusts custom certificate and allows application to intercept the traffic.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With