Question

I have been searching for this for a few weeks and can't seem to find an answer anywhere. I am trying to do the following for Android. This code is from a C# app I wrote but am porting it to Android. The web endpoint requires a cert to be attached to the request for mutual authentication to make the web service call.

        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;

I am then doing this (request is an HttpWebRequest):

request.ClientCertificates.Add(cert);

This works fine in C# however when I move to Android I'm getting 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 to put fiddler between the emulator and the endpoint and it comes back with a 200. I think this is because my cert is in my local private store on my dev machine. Any thoughts?

Was it helpful?

Solution

OK. I found the answer to this one. The issue lies in that the self signed cert can't be used until it exists in the android TrustStore. However the default TrustStore is read-only after an app starts so it's hard to modify it. I was setting up my own custom trust store, however the root certs were not part of it, so calls out to any https would fail. The solution came from this blog post:

http://nelenkov.blogspot.com/2011/12/using-custom-certificate-trust-store-on.html

Long story short, setup a custom TrustStore that contains the self signed cert, then export all the certificates from the default trust store and import them into the custom trust store. Then use that trust store to setup your SSL Context (also need to use a custom keystore since the client certificate needs to be attached to the request as well). I believe if I would not allow self signed certs this wouldn't be a big deal as the root cert for the client certificate would exist in the default TrustStore (or at least I hope it would).

OTHER TIPS

Also you should never hardcode paths :

 InputStream caInput = new BufferedInputStream(new FileInputStream("/sdcard/Certificate.pfx"));

Instead use:

 Environment.getExternalStorageDirectory()

Look this : http://developer.android.com/training/basics/data-storage/files.html

Also for certificates Take a look at this: Using a self-signed certificate to create a secure client-server connection in android

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top