Android and client certificates
I've been searching for weeks and can't seem to find an answer anywhere. I am trying to do the following for Android. The code is from a C# app I wrote but am porting it to Android. Web endpoints require certificates to be attached to mutual authentication requests for web service calls.
string certThumbprint = "E1313F6A2D770783868755D016CE748F6A9B0028";
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
try
{
certStore.Open(OpenFlags.ReadOnly);
}
catch (Exception e)
{
if (e is CryptographicException)
{
Console.WriteLine("Error: The store is unreadable.");
}
else if (e is SecurityException)
{
Console.WriteLine("Error: You don't have the required permission.");
}
else if (e is ArgumentException)
{
Console.WriteLine("Error: Invalid values in the store.");
}
else
{
throw;
}
}
X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, certThumbprint, false);
certStore.Close();
if (0 == certCollection.Count)
{
throw new Exception("Error: No certificate found containing thumbprint " + certThumbprint);
}
X509Certificate2 certificate = certCollection[0];
return certificate;
Then, I'm doing this (the request is an HttpWebRequest):
request.ClientCertificates.Add(cert);
This works fine in C#, but when I go to Android I get a "file not found" error on the getInputStream() call. Here is my Android code:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(new FileInputStream("/sdcard/Certificate.pfx"));
KeyHelper kh = new KeyHelper();
Certificate ca = kh.GetKey("Password");
String keyStoreType = KeyStore.getDefaultType();
keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "Password".toCharArray());
SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(),null,new SecureRandom());
HttpsURLConnection urlConnection =
(HttpsURLConnection)url.openConnection();
urlConnection.setRequestProperty("x-ms-version",AZURE_REST_VERSION);
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
urlConnection.setRequestMethod("GET");
urlConnection.setSSLSocketFactory(context.getSocketFactory());
urlConnection.connect();
InputStream in = new BufferedInputStream(urlConnection.getInputStream()); //<-----Blows up here
} catch (KeyStoreException e) {
throw new KeyStoreException("Keystore Exception",e);
} catch (NoSuchAlgorithmException e) {
throw new NoSuchAlgorithmException("Algorithm exception",e);
} catch (KeyManagementException e) {
throw new KeyManagementException("Key Exception", e);
}
I tried putting fiddler between the emulator and the endpoint and it returned 200. I think this is because my certificates are in a local private store on the development machine. Any ideas?
Row. I found the answer. The problem is that the self-signed certificate cannot be used unless it exists in the Android TrustStore. However, the default TrustStore is read-only after the app starts, making it difficult to modify it. I'm building my own custom truststore, but the root certificate is not part of it, so any calls to https will fail. The solution comes from this blog post:
http://nelenkov.blogspot.com/2011/12/using-custom-certificate-trust-store-on.html
In short, set up a custom TrustStore containing self-signed certificates, then export all certificates from the default truststore and import them into the custom truststore. Then, use that truststore to set up your SSL context (you will also need to use a custom keystore since the client certificate needs to be appended to the request as well). I believe that if I don't allow self-signed certificates then it won't be a big deal, since the root certificate for the client certificate will be present in the default TrustStore (or at least hope so).