Android and Client Certificates

落花浮王杯 提交于 2020-02-28 07:55:29

问题


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?


回答1:


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).




回答2:


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



来源:https://stackoverflow.com/questions/20747873/android-and-client-certificates

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!