KeyChain API on Android 4.1 and client certificate authentication

classic Classic list List threaded Threaded
13 messages Options
Reply | Threaded
Open this post in threaded view
|

KeyChain API on Android 4.1 and client certificate authentication

Nikola
Hi,

I need to issue HTTP requests against a server that requires authentication with client certificates. So far (on Android 4.0), I've been using KeyChain API to get the certificate and the associated private key:

         X509Certificate[] certificateChain = KeyChain.GetCertificateChain(this, alias);
         IPrivateKey privateKey = KeyChain.GetPrivateKey(this, alias);

Then, I've used the following code to get raw representation of both the certificate chain and the private key so that I can create .NET X509Certificate2 instance out of them:

         KeyStore pkcs12KeyStore = KeyStore.GetInstance("PKCS12");
         pkcs12KeyStore.Load(null, null);
         pkcs12KeyStore.SetKeyEntry(alias, privateKey, null, certificateChain);

         Java.Security.Cert.X509Certificate javaCertificate = (Java.Security.Cert.X509Certificate)pkcs12KeyStore.GetCertificate(alias);

         PKCS8.PrivateKeyInfo privateKeyInfo = new PKCS8.PrivateKeyInfo(privateKey.GetEncoded());
         RSA privatekeyRsa = PKCS8.PrivateKeyInfo.DecodeRSA(privateKeyInfo.PrivateKey);

         X509Certificate2 certificate = new X509Certificate2(javaCertificate.GetEncoded());
         certificate.PrivateKey = privatekeyRsa;        

I was then able to create and issue web requests with this certificate using standard .NET's HttpWebRequest class:

         HttpWebRequest request = (HttpWebRequest)WebRequest.Create(@"url");
         request.ClientCertificates.Add(certificate);

Now, the problem: On Android 4.1, this code stopped working. It is no longer possible to get raw representation of private key (privateKey.GetEncoded() returns null), therefore, I can no longer convert it to .NET's X509Certificate2 instance.

Is there some alternative that would allow me to create X509Certificate2 with the KeyChain API?

Thank you very much for any input!
Reply | Threaded
Open this post in threaded view
|

Re: KeyChain API on Android 4.1 and client certificate authentication

Atsushi Eno
Hello,

This seems to be common to Java and Android 4.1 issue. I found these
StackOverflow. Maybe you can use some technique described there (either
by using [DllImport], or create jar for them and use binding library).

http://stackoverflow.com/questions/11261774/using-android-4-1-keychain
http://stackoverflow.com/questions/12507697/keychain-getprivatekeycontext-string-on-android-4-1

Atsushi Eno

Nikola wrote:

> Hi,
>
> I need to issue HTTP requests against a server that requires authentication
> with client certificates. So far (on Android 4.0), I've been using KeyChain
> API to get the certificate and the associated private key:
>
>           X509Certificate[] certificateChain =
> KeyChain.GetCertificateChain(this, alias);
>           IPrivateKey privateKey = KeyChain.GetPrivateKey(this, alias);
>
> Then, I've used the following code to get raw representation of both the
> certificate chain and the private key so that I can create .NET
> X509Certificate2 instance out of them:
>
>           KeyStore pkcs12KeyStore = KeyStore.GetInstance("PKCS12");
>           pkcs12KeyStore.Load(null, null);
>           pkcs12KeyStore.SetKeyEntry(alias, privateKey, null,
> certificateChain);
>
>           Java.Security.Cert.X509Certificate javaCertificate =
> (Java.Security.Cert.X509Certificate)pkcs12KeyStore.GetCertificate(alias);
>
>           PKCS8.PrivateKeyInfo privateKeyInfo = new
> PKCS8.PrivateKeyInfo(privateKey.GetEncoded());
>           RSA privatekeyRsa =
> PKCS8.PrivateKeyInfo.DecodeRSA(privateKeyInfo.PrivateKey);
>
>           X509Certificate2 certificate = new
> X509Certificate2(javaCertificate.GetEncoded());
>           certificate.PrivateKey = privatekeyRsa;
>
> I was then able to create and issue web requests with this certificate using
> standard .NET's HttpWebRequest class:
>
>           HttpWebRequest request = (HttpWebRequest)WebRequest.Create(@"url");
>           request.ClientCertificates.Add(certificate);
>
> *Now, the problem:* On Android 4.1, this code stopped working. It is no
> longer possible to get raw representation of private key
> (privateKey.GetEncoded() returns null), therefore, I can no longer convert
> it to .NET's X509Certificate2 instance.
>
> Is there some alternative that would allow me to create X509Certificate2
> with the KeyChain API?
>
> Thank you very much for any input!
>
>
>
> --
> View this message in context: http://mono-for-android.1047100.n5.nabble.com/KeyChain-API-on-Android-4-1-and-client-certificate-authentication-tp5712844.html
> Sent from the Mono for Android mailing list archive at Nabble.com.
> _______________________________________________
> Monodroid mailing list
> [hidden email]
>
> UNSUBSCRIBE INFORMATION:
> http://lists.ximian.com/mailman/listinfo/monodroid
>
>
>

_______________________________________________
Monodroid mailing list
[hidden email]

UNSUBSCRIBE INFORMATION:
http://lists.ximian.com/mailman/listinfo/monodroid
Reply | Threaded
Open this post in threaded view
|

Re: KeyChain API on Android 4.1 and client certificate authentication

Nikola
Hi,

thank you for you answer. I would be grateful if you could elaborate a bit, because I am not sure how would using DllImport help me with this.

To rephrase - so far, I've been "translating" the Java.Security.IPrivateKey instance to System.Security.Cryptography.RSA instance by converting the IPrivateKey to raw bytes and working from there (as you can see in the code samples). That enabled me to use HttpWebRequest.

With Android 4.1, this is no longer possible, since I can no longer get my hands on raw byte representation of the private key. The question is: is there any alternative way I could use HttpWebRequestwith Java.Security.IPrivateKey?

Thank you,

Nikola Anusev
Reply | Threaded
Open this post in threaded view
|

Re: KeyChain API on Android 4.1 and client certificate authentication

Jonathan Pryor-2
On Feb 7, 2013, at 12:40 PM, Nikola <[hidden email]> wrote:
> thank you for you answer. I would be grateful if you could elaborate a bit, because I am not sure how would using DllImport help me with this.

The first url [0] referenced an answer [1] which referenced using `libkeystore.so`, which has a C function you could P/Invoke [2]: Java_de_blinkt_openvpn_OpenVpnManagementThread_rsasign.

Well, in theory you could P/Invoke. In practice, this would be difficult as we don't currently expose the JNIEnv* that you'd need in order to actually invoke that function, so this is problematic. But in theory, P/Invoke could do it. ;-)

They also suggest using Java reflection to access private methods (the mentioned processSignJellyBeans() method, now at [3]), which could be translated to JNIEnv.

The second url [4, 5] implies that it may not be possible at all, because the private key may be stored in  hardware and inaccessible to your app.

 - Jon

[0]: http://stackoverflow.com/questions/11261774/using-android-4-1-keychain
[1]: http://stackoverflow.com/a/11387344/83444
[2]: http://code.google.com/p/ics-openvpn/source/browse/jni/jbcrypto.cpp#L20
[3]: http://code.google.com/p/ics-openvpn/source/browse/src/de/blinkt/openvpn/OpenVpnManagementThread.java#523
[4]: http://stackoverflow.com/questions/12507697/keychain-getprivatekeycontext-string-on-android-4-1
[5]: http://stackoverflow.com/a/12539380/83444

_______________________________________________
Monodroid mailing list
[hidden email]

UNSUBSCRIBE INFORMATION:
http://lists.ximian.com/mailman/listinfo/monodroid
Reply | Threaded
Open this post in threaded view
|

Re: KeyChain API on Android 4.1 and client certificate authentication

Nikola
:) I got the same impression from the second URL. From what you say I gather the P/Invoke is basically a no-go, so I think I will have to say goodbye to HttpWebRequest.

I've tried to use HttpsUrlConnection (http://androidapi.xamarin.com/?link=T%3aJavax.Net.Ssl.HttpsURLConnection) instead, but unfortunately, I am getting very similar results.

The code:

    X509Certificate[] certificateChain = KeyChain.GetCertificateChain(this, alias);
    IPrivateKey privateKey = KeyChain.GetPrivateKey(this, alias);
   
    KeyStore clientCertificateKeyStore = KeyStore.GetInstance("PKCS12");
    clientCertificateKeyStore.Load(null, null);
    clientCertificateKeyStore.SetKeyEntry(alias, privateKey, null, certificateChain);
    KeyManagerFactory clientKeysFactory = KeyManagerFactory.GetInstance("X509");
    clientKeysFactory.Init(clientCertificateKeyStore, null);
   
    // this paragraph is probably irrelevant, just including it here for completeness
    KeyStore serverKeysStore = KeyStore.GetInstance("AndroidCAStore");
    serverKeysStore.Load(null, null);
    TrustManagerFactory serverTrustManagerFactory = TrustManagerFactory.GetInstance(TrustManagerFactory.DefaultAlgorithm);
    serverTrustManagerFactory.Init(serverKeysStore);
   
    SSLContext context = SSLContext.GetInstance("TLS");
    context.Init(clientKeysFactory.GetKeyManagers(), serverTrustManagerFactory.GetTrustManagers(), new SecureRandom());

    URL url = new URL("url");
    HttpsURLConnection urlConnection = (HttpsURLConnection)url.OpenConnection();
    urlConnection.SSLSocketFactory = context.SocketFactory;
    string contents = new StreamReader(urlConnection.InputStream).ReadToEnd();

The last line throws the following mysterious exception:

    {System.NullReferenceException: Object reference not set to an instance of an object
      at Android.Runtime.JNIEnv.CallObjectMethod (IntPtr jobject, IntPtr jmethod) [0x00000] in     /Users/builder/data/lanes/monodroid-mac-monodroid-4.4-    series/6418373f/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.g.cs:129
      at Java.Net.URLConnection.get_InputStream () [0x0003e] in /Users/builder/data/lanes/monodroid-mac-    monodroid-4.4-series/6418373f/source/monodroid/src/Mono.Android/platforms/android-    14/src/generated/Java.Net.URLConnection.cs:689
      at AndroidApplication8.Activity1.AndroidWayConnection (System.String alias) [0x000c0] in     C:\Users\anusevn\Documents\Visual Studio 2010\Projects\AndroidApplication8\Activity1.cs:116 }

Then I've noticed the ResponseMessage property of HttpsUrlConnection contains something more helpful:

    Javax.Net.Ssl.SSLException: Error checking private key: ssl=0x6703a610:
    error:140A30BE:SSL routines:SSL_check_private_key:no private key assigned     (external/openssl/ssl/ssl_lib.c:926 0x645ab7de:0x00000000)

Exactly the same code works on Android 4.0. Seems that HttpsUrlConnection and related factories rely on the ability to get the private key, just as the HttpWebRequest does, or maybe there is some other underlying issue. This seems like a Mono for Android bug to me, because it is certainly possible to use HttpsUrlConnection with native Android code.

Is there any way at all to connect to client certificate authenticated HTTPS site with Mono for Android?
Reply | Threaded
Open this post in threaded view
|

Re: KeyChain API on Android 4.1 and client certificate authentication

Jonathan Pryor-2
On Feb 8, 2013, at 7:41 AM, Nikola <[hidden email]> wrote:
> I've tried to use HttpsUrlConnection (http://androidapi.xamarin.com/?link=T%3aJavax.Net.Ssl.HttpsURLConnection) instead, but unfortunately, I am getting very similar results.

Unfortunately, I cannot use your code as-is, as it's missing crucial contextual information, e.g. what is the value of `alias`, and what (if any) device configuration do I need to do to make it work? In short, a complete sample to demonstrate the bug would be handy....for this aspect.

That said...

> Is there *any* way at all to connect to client certificate authenticated HTTPS site with Mono for Android?

Is there any particular reason you're using the Java/Android stack for this? You could instead stick to the .NET stack. For example, I created a sample project, embedded my certificate as an EmbeddedResource, then subscribed to the ServicePointManager.ServerCertificateValidationCallback callback:

    using System;
    using System.IO;
    using System.Net;
    using System.Net.Security;
    using System.Runtime.InteropServices;
    using System.Security.Cryptography.X509Certificates;

    using Android.App;
    using Android.Content;
    using Android.Runtime;
    using Android.Views;
    using Android.Widget;
    using Android.OS;

    namespace Scratch.CustomCert
    {
        static class MyCerts {
            static MyCerts ()
            {
                // TODO: update resource name as appropriate
                using (var s = typeof(MyCerts).Assembly.GetManifestResourceStream ("cert.dat")) {
                    var d = new byte [s.Length];
                    s.Read (d, 0, d.Length);
                    MyCert = new X509Certificate2 (d);
                }
            }

            public static readonly X509Certificate2 MyCert;
        }

        [Activity (Label = "Scratch.CustomCert", MainLauncher = true)]
        public class Activity1 : Activity
        {
            int count = 1;

            protected override void OnCreate (Bundle bundle)
            {
                base.OnCreate (bundle);

                // Set our view from the "main" layout resource
                SetContentView (Resource.Layout.Main);

                // Get our button from the layout resource,
                // and attach an event to it
                Button button = FindViewById<Button> (Resource.Id.myButton);

                ServicePointManager.ServerCertificateValidationCallback = RemoteCertificateValidationCallback;
               
                button.Click += delegate {
                    button.Text = string.Format ("{0} clicks!", count++);

                    try {
                        // TODO: update URL as appropriate.
                        var w = HttpWebRequest.Create ("https://example.com/");
                        using (var response = w.GetResponse ())
                        using (var r = new StreamReader (response.GetResponseStream ())) {
                            button.Text = r.ReadToEnd ();
                        }
                    } catch (Exception e) {
                        Console.WriteLine ("error: {0}", e);
                    }
                };
            }

            bool RemoteCertificateValidationCallback(
                    object sender,
                    X509Certificate certificate,
                    X509Chain chain,
                    SslPolicyErrors sslPolicyErrors)
            {
                if (MyCerts.MyCert.Equals (certificate))
                    return true;

                return false;
            }
        }
    }


Using the .NET networking stack and using the ServicePointManager.ServerCertificateValidationCallback callback allows me to connect to a site which uses a self-signed certificate (and thus would normally cause Android all sorts of conniptions).

 - Jon

_______________________________________________
Monodroid mailing list
[hidden email]

UNSUBSCRIBE INFORMATION:
http://lists.ximian.com/mailman/listinfo/monodroid
Reply | Threaded
Open this post in threaded view
|

Re: KeyChain API on Android 4.1 and client certificate authentication

Nikola
Jon,

> Using the .NET networking stack and using the ServicePointManager.ServerCertificateValidationCallback callback allows me to connect to a site which uses a self-signed certificate

This is not what I want to achieve. What am I trying to do here is, I believe, "officially" called Mutual SSL authentication. In case this kind of authentication is used, the SSL handshake consists of two additional steps - quoting from http://publib.boulder.ibm.com/infocenter/wmqv6/v6r0/index.jsp?topic=%2Fcom.ibm.mq.csqzas.doc%2Fsy10660_.htm, that would be steps 5 and 6:

5. If the SSL server sent a "client certificate request", the SSL client sends a random byte string encrypted with the client's private key, together with the client's digital certificate, or a "no digital certificate alert". This alert is only a warning, but with some implementations the handshake fails if client authentication is mandatory.
6. The SSL server verifies the signature on the client certificate.

This is what I am having problems with. I am sorry, but I don't see how your sample project would help me -  I believe ServerCertificateValidationCallback fires for step 3 of SSL handshake (refering to the IBM document above). My problem has nothing to do with server-side certificiates.

I am pretty sure you are already familiar with how the SSL handshake works :), I just want to be absolutely positive we are on the same page here.

With that in mind, I'll try to rephrase the problem again.
To complete the SSL handshake, it is necessary to encypt some random piece of data with client's private key. The private key used for the encryption is normally stored in X509Certificate2.PrivateKey property - when using such X509Certificate2 with HttpWebRequest, the SSL handshake completes successfully and everything is great.

On Android, my app uses the KeyChain to get its hands on the certificate and private key. Now, to be able to use .NET HTTP stack and Mutual SSL authentication, I have to create an X509Certificate2 instance and set it up with a private key from the KeyChain. To do that, I have to convert that key to raw bytes, which is no longer possible with Android 4.1 (my original post). So as an alternative, I tried to use Android HTTP stack and hoped it will be successful since there is no private key converting involved. However, that failed, too (post #4). And now, I seem to be out of options :(

> Unfortunately, I cannot use your code as-is, as it's missing crucial contextual information, e.g. what is the value of `alias`, and what (if any) device configuration do I need to do to make it work?

The value of 'alias' depends on the exact client certificate being used. In my case, it is "client02", but I suppose that won't help you much :) It is not that simple to provide you with a complete sample - generally spekaing, to reproduce this, there needs to be a complete environment with HTTPS server requiring client certificates as a part of SSL handshake and any Android 4.1 device with the proper client certificate installed. If you think it would help to resolve this, I can prepare such an environment relatively quickly.
Reply | Threaded
Open this post in threaded view
|

Re: KeyChain API on Android 4.1 and client certificate authentication

Jonathan Pryor-2
On Feb 14, 2013, at 3:20 PM, Nikola <[hidden email]> wrote:
> This is not what I want to achieve. What am I trying to do here is, I believe, "officially" called Mutual SSL authentication.
...
> I am pretty sure you are already familiar with how the SSL handshake works :), I just want to be absolutely positive we are on the same page here.

This is very helpful context. Thank you. :-)

I'm not nearly as familiar with SSL as would be handy at times like this...

> With that in mind, I'll try to rephrase the problem again.  To complete the SSL handshake, it is necessary to encypt some random piece of data with client's private key. The private key used for the encryption is normally stored in X509Certificate2.PrivateKey property - when using such X509Certificate2 with HttpWebRequest, the SSL handshake completes
> successfully and everything is great.
>
> On Android, my app uses the KeyChain to get its hands on the certificate and private key.

Obvious question: Why do you need to use the KeyChain API? Why not just include your client certificate in your app and then use it?

That aside...

> Now, to be able to use .NET HTTP stack and Mutual SSL authentication, I have to create an X509Certificate2 instance and set it up with a private key from the KeyChain. To do that, I have to convert that key to raw bytes, which is no longer possible with Android 4.1 (my original post).

I believe that this behavior isn't a Mono for Android bug, but an Android change (as per your description, behavior is dependent upon the Android version). Which is known; the question thus becomes how to workaround Android's breakage.

Related: We DID have a bug in KeyStore.Load() handling (no bug number), in which a NullReferenceException would be thrown when calling KeyStore.Load(null, null). This should be fixed in 4.4.x (not sure exact fix date). However, I don't think you're hitting this, as the stack trace you provided doesn't match the one in the commit message...

Related 2: your earlier reported exception that smells like a bug, but I can't repro it (and thus would love one):

>   {System.NullReferenceException: Object reference not set to an instance of an object
>      at Android.Runtime.JNIEnv.CallObjectMethod (IntPtr jobject, IntPtrjmethod)
>      at Java.Net.URLConnection.get_InputStream ()
>     ...


By any chance is your code using multiple threads?

> It is not that simple to provide you with a complete sample - generally spekaing, to reproduce this, there needs to be a complete environment with HTTPS server requiring client certificates as a part of SSL handshake and any Android 4.1 device with the proper client certificate installed.

<groan>

> If you think it would help to resolve this, I can prepare such an environment relatively quickly.

Unfortunately it looks like it's not possible to reproduce the problem without the corresponding environment. With reference to an earlier message about using JNIEnv to invoke the private Android methods, I could provide you that code, but I'd have no way to test it to see that it actually works.

I'll provide that code in a bit; if that doesn't help you, let's see about getting a full repro...

Thanks,
 - Jon

_______________________________________________
Monodroid mailing list
[hidden email]

UNSUBSCRIBE INFORMATION:
http://lists.ximian.com/mailman/listinfo/monodroid
Reply | Threaded
Open this post in threaded view
|

Re: KeyChain API on Android 4.1 and client certificate authentication

Nikola
> This is very helpful context. Thank you. :-)

You are welcome :) Glad that it helped to bring my point across, with English being my second language I sometimes wonder if my mails are clear enough.

> Obvious question: Why do you need to use the KeyChain API? Why not just include your client certificate in your app and then use it?

I am now doing exactly that to temporarily work around the issue. Unfortunately, this is not acceptable as a long-term solution; enterprise customers that are mostly using our app insist on having the KeyChain support implemented, because it greatly simplifies management of client certificates over a large number of devices - they are able to employ various MDM solutions to help with that (e.g. http://www.air-watch.com/solutions/android).

> I believe that this behavior isn't a Mono for Android bug, but an Android change (as per your description, behavior is dependent upon the Android version).

I totally agree with that. However, the change in Android effectively makes it impossible to use .NET HTTP stack with KeyChain API. I think what is needed here is an ability to somehow override the default implementation of SSL handshake to be able to sign those random bytes in a custom way - with that, I could use Signature class (http://androidapi.xamarin.com/?link=T%3aJava.Security.Signature) to do the signing without converting the private key. Is there any chance you will extend the HTTP stack to allow this?

> By any chance is your code using multiple threads?

Actually, it is. It is not possible to simply select the certificate from the KeyChain with an arbitrary alias. The method http://androidapi.xamarin.com/?link=M%3aAndroid.Security.KeyChain.ChoosePrivateKeyAlias must be used first to let the user select the certificate; the alias that the user chose is then available in the callback method (last parameter). I believe the callback is not executed in the UI thread.

> I'll provide that code in a bit;

That would be awesome!

> if that doesn't help you, let's see about getting a full repro...

Ok, I will put together some test environment ASAP along with a sample client app. Will keep you updated.
Reply | Threaded
Open this post in threaded view
|

Re: KeyChain API on Android 4.1 and client certificate authentication

Jonathan Pryor-2
In reply to this post by Jonathan Pryor-2
On Feb 14, 2013, at 4:00 PM, Jonathan Pryor <[hidden email]> wrote:
> I'll provide that code in a bit; if that doesn't help you, let's see about getting a full repro...

Bwa-ha-ha-ha-ha-ah-ha *cough* *splutter*

Firstly, the links I previously provided have been invalidated due to refactoring (yay). That'll teach me to provide links into "master" instead of a proper revision. The mythical processSignJellyBeans() method is now at:

        http://code.google.com/p/ics-openvpn/source/browse/src/de/blinkt/openvpn/VpnProfile.java?spec=svn77efc1af3b4d7a02864f21eb93a92f8568e9f5f0&r=77efc1af3b4d7a02864f21eb93a92f8568e9f5f0#806

The processSignJellyBeans() method does two things:

1. Extract the "integer pointer to EVP_pkey" from the PrivateKey.
2. Sign data with it.

(1) is reasonably straightforward:

        // ~line by line port of part of processSignJellyBeans():
        // http://code.google.com/p/ics-openvpn/source/browse/src/de/blinkt/openvpn/VpnProfile.java?spec=svn77efc1af3b4d7a02864f21eb93a92f8568e9f5f0&r=77efc1af3b4d7a02864f21eb93a92f8568e9f5f0#806
        public static int GetPrivateKeyHandle (IPrivateKey privkey)
        {
            var _privkey                        = privkey.JavaCast<Java.Lang.Object>();
            IntPtr privkey_class                = _privkey.Class.Superclass.Handle;
            IntPtr getKey                       = JNIEnv.GetMethodID (privkey_class, "getOpenSSLKey", "()Lorg/apache/harmony/xnet/provider/jsse/OpenSSLKey;");
            IntPtr opensslkey                   = JNIEnv.CallObjectMethod (privkey.Handle, getKey);
            IntPtr opensslkey_class             = JNIEnv.GetObjectClass (opensslkey);
            IntPtr opensslkey_getPkeyContext    = JNIEnv.GetMethodID (opensslkey_class, "getPkeyContext", "()I");
            int pkey                            = JNIEnv.CallIntMethod (opensslkey, opensslkey_getPkeyContext);

            JNIEnv.DeleteLocalRef (opensslkey);
            JNIEnv.DeleteLocalRef (opensslkey_class);

            return pkey;
        }

The only "requirement" here is having an Android source checkout to help create the JNI method signatures for method lookup.

(2) is the "interesting" bit, and by "interesting" I mean "start praying to your favorite deity."

What ics-openvpn's de.blinkt.openvpn.VpnProfile.processSignJellyBeans() method does is call the `native` VpnProfile.rsasign() method, which (as mentioned earlier) isn't entirely possible right now because JNIEnv.Handle isn't public.

That's actually easily worked around; just use .NET reflection to grab the JNIEnv.Handle property value. However, that's not the real problem.

Alternatively, the native VpnProfile.rsasign() function could be ported to C#. I don't think this would be too difficult:

        http://code.google.com/p/ics-openvpn/source/browse/jni/jbcrypto.cpp#44

Again, that's not the real problem.

The real problem is that it looks like ics-openvpn works by embedding a COPY of openssl into ics-openvpn!

        http://code.google.com/p/ics-openvpn/source/browse#hg%2Fopenssl

We are no longer in the realm of Java vs. C#. We're in the realm of "what dependencies do you want your project to have?"

If your app can depend on an ics-openvpn binary/package being installed on the target device (can it?), then you should be able to derive a P/Invoke into libopvpnutil.so!Java_de_blinkt_openvpn_VpnProfile_rsasign():

        [DllImport ("/data/data/@TODO_ics-openvpn_PACKAGE_NAME@/lib/libopenvpnutil.so")]
        static extern IntPtr Java_de_blinkt_openvpn_VpnProfile_rsasign (IntPtr env, IntPtr klass, IntPtr array, int pkeyRef);

        public static IntPtr GetJniEnvHandle ()
        {
            var handleP = typeof(JNIEnv).GetProperty ("Handle", BindingFlags.NonPublic | BindingFlags.Static);
            if (handleP == null)
                throw new NotSupportedException ("This doesn't look like Mono for Android...");
            var handle = (IntPtr) handleP.GetValue (null, null);
            if (handle == IntPtr.Zero)
                throw new InvalidOperationException ("Unpossible!");
            return handle;
        }

        ...

        byte[] data = ...
        IntPtr javaData = JNIEnv.NewArray(data);
        IntPtr signedJavaData = Java_de_blinkt_openvpn_VpnProfile_rsasign (GetJniEnvHandle (), IntPtr.Zero, javaData, GetPrivateKeyHandle (privkey));
        byte[] signedData = JNIEnv.GetArray<byte>(signedJavaData);
        JNIEnv.DeleteLocalRef(signedJavaData);

If your app can't depend on ics-openvpn being installed...then you need to do what they're doing: embed openssl into your app, at which  point you can port Java_de_blinkt_openvpn_VpnProfile_rsasign() to C# and just P/Invoke into your embedded openssl for the implementation.

What's "interesting" is that stackoverflow suggested that you could use libkeystore.so, but I don't see how this would help (particularly as I don't see an RSA_sign() export in libkeystore.so):

        http://stackoverflow.com/a/11387344/83444

So I fail to see how libkeystore.so would be useful here. :-/

All of the above makes me wonder if we're missing something. android.security.KeyStore looks entirely useless, as you can't get anything out of it, so what's the point?

It would appear, then, that using HttpsURLConnection is (hopefully?) the way to go.

By any chance could you create a small repro for NullReferenceException? Or does it only occur in this environment?

 - Jon

_______________________________________________
Monodroid mailing list
[hidden email]

UNSUBSCRIBE INFORMATION:
http://lists.ximian.com/mailman/listinfo/monodroid
Reply | Threaded
Open this post in threaded view
|

Re: KeyChain API on Android 4.1 and client certificate authentication

Jonathan Pryor-2
In reply to this post by Nikola
On Feb 14, 2013, at 4:45 PM, Nikola <[hidden email]> wrote:
> I think what is needed here is an ability to somehow override the default implementation of SSL handshake to be able to sign those random bytes in a custom way

I'm told by the resident SSL expert (not me!) that HttpWebRequest.ClientCertificates can reference a X509Certificate2 instance, and X509Certificate2.PrivateKey can reference a System.Security.Cryptography.RSA instance, and this RSA instance will be used in the SSL handshake logic.

(At least I think I've got that straight.)

So this should be possible.

> Is there any chance you will extend the HTTP stack to allow this?

I don't see us extending the .NET API in this area, except maybe via the existing Mono.Security types.

 - Jon

_______________________________________________
Monodroid mailing list
[hidden email]

UNSUBSCRIBE INFORMATION:
http://lists.ximian.com/mailman/listinfo/monodroid
Reply | Threaded
Open this post in threaded view
|

Re: KeyChain API on Android 4.1 and client certificate authentication

Nikola
I've sent you all necessary details to reproduce the NullReference exception.

> I'm told by the resident SSL expert ... and this RSA instance will be used in the SSL handshake logic.

I've tried to inherit from System.Security.Cryptography.RSA and use the IPrivateKey in this custom class to do the signing - CustomRSA.cs.

This is the code that attempts to use the custom RSA implementation:

   X509Certificate[] certificateChain = KeyChain.GetCertificateChain(this, alias);
   IPrivateKey privateKey = KeyChain.GetPrivateKey(this, alias);
   
   KeyStore pkcs12KeyStore = KeyStore.GetInstance("PKCS12");
   pkcs12KeyStore.Load(null, null);
   pkcs12KeyStore.SetKeyEntry(alias, privateKey, null, certificateChain);
   
   X509Certificate javaCertificate = (X509Certificate)pkcs12KeyStore.GetCertificate(alias);
   
   X509Certificate2 certificate = new X509Certificate2(javaCertificate.GetEncoded());
   certificate.PrivateKey = new CustomRSA(privateKey, javaCertificate);
   
   ServicePointManager.ServerCertificateValidationCallback += ((sender, c, chain, sslPolicyErrors) => true);
   
   HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serverUrl);
   request.ClientCertificates.Add(certificate);
   request.GetResponse();

The last line fails with the following horrible exception:

System.Net.WebException: Error getting response stream (Write: The authentication or decryption has failed.): SendFailure
 ---> System.IO.IOException: The authentication or decryption has failed.
 ---> Java.Lang.ArrayIndexOutOfBoundsException: Exception of type 'Java.Lang.ArrayIndexOutOfBoundsException' was thrown.
  at Android.Runtime.JNIEnv.CallObjectMethod (IntPtr jobject, IntPtr jmethod, Android.Runtime.JValue[] parms) [0x00024] in /Users/builder/data/lanes/monodroid-mac-monodroid-4.4-series/6418373f/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.g.cs:145
  at Javax.Crypto.Cipher.DoFinal (System.Byte[] input) [0x00034] in /Users/builder/data/lanes/monodroid-mac-monodroid-4.4-series/6418373f/source/monodroid/src/Mono.Android/platforms/android-14/src/generated/Javax.Crypto.Cipher.cs:141
  at AndroidApplication8.CustomRSA.DecryptValue (System.Byte[] rgb) [0x00025] in C:\Users\anusevn\Documents\Visual Studio 2010\Projects\AndroidApplication8\CustomRSA.cs:62
  at Mono.Security.Cryptography.PKCS1.RSASP1 (System.Security.Cryptography.RSA rsa, System.Byte[] m) [0x00000] in <filename unknown>:0
  at Mono.Security.Cryptography.PKCS1.Sign_v15 (System.Security.Cryptography.RSA rsa, System.Security.Cryptography.HashAlgorithm hash, System.Byte[] hashValue) [0x00000] in <filename unknown>:0
  at Mono.Security.Protocol.Tls.RSASslSignatureFormatter.CreateSignature (System.Byte[] rgbHash) [0x00000] in <filename unknown>:0
  at Mono.Security.Cryptography.MD5SHA1.CreateSignature (System.Security.Cryptography.RSA rsa) [0x00000] in <filename unknown>:0
  at Mono.Security.Protocol.Tls.Handshake.Client.TlsClientCertificateVerify.ProcessAsTls1 () [0x00000] in <filename unknown>:0
  at Mono.Security.Protocol.Tls.Handshake.HandshakeMessage.Process () [0x00000] in <filename unknown>:0
  at Mono.Security.Protocol.Tls.RecordProtocol.BeginSendRecord (HandshakeType handshakeType, System.AsyncCallback callback, System.Object state) [0x00000] in <filename unknown>:0
  at Mono.Security.Protocol.Tls.RecordProtocol.SendRecord (HandshakeType type) [0x00000] in <filename unknown>:0
  at Mono.Security.Protocol.Tls.SslClientStream.OnNegotiateHandshakeCallback (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0
  at Mono.Security.Protocol.Tls.SslStreamBase.AsyncHandshakeCallback (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0
--- End of managed exception stack trace ---
java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block
  at com.android.org.bouncycastle.jce.provider.JCERSACipher.engineDoFinal(JCERSACipher.java:450)
  at javax.crypto.Cipher.doFinal(Cipher.java:1111)
  at dalvik.system.NativeStart.run(Native Method)
--- End of inner exception stack trace ---
  at Mono.Security.Protocol.Tls.SslStreamBase.AsyncHandshakeCallback (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0
--- End of inner exception stack trace ---
  at System.Net.HttpWebRequest.EndGetResponse (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0
  at System.Net.HttpWebRequest.GetResponse () [0x00000] in <filename unknown>:0
  at AndroidApplication8.Activity1.ConnectWithNetStack (System.String alias) [0x000af] in C:\Users\anusevn\Documents\Visual Studio 2010\Projects\AndroidApplication8\Activity1.cs:115

I am not really sure why the Mono.Security.Cryptography.PKCS1.Sign_v15 method calls the CustomRSA.DecryptValue method - I would expect that signing would involve encryption of bytes, not their decryption. But it's almost certain I am doing something wrong when inheriting from the RSA class - could your SSL expert perhaps take a look? :)
Reply | Threaded
Open this post in threaded view
|

Re: KeyChain API on Android 4.1 and client certificate authentication

Jonathan Pryor-2
On Feb 18, 2013, at 10:17 AM, Nikola <[hidden email]> wrote:
> I've sent you all necessary details to reproduce the NullReference exception.

Thank you. I'll investigate this tomorrow.

>> I'm told by the resident SSL expert ... and this RSA instance will be used in the SSL handshake logic.
...
> The last line fails with the following horrible exception:
>
> System.Net.WebException: ... ---> Java.Lang.ArrayIndexOutOfBoundsException: Exception of type 'Java.Lang.ArrayIndexOutOfBoundsException' was thrown.
...
> java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block
>  at com.android.org.bouncycastle.jce.provider.JCERSACipher.engineDoFinal(JCERSACipher.java:450)
>  at javax.crypto.Cipher.doFinal(Cipher.java:1111)
...

As with many things in Android, you need to (1) read _very_ carefully, and (2) frequently need to dig into the Android source code.

Regarding (1), the error is that you're passing "too much data for RSA block."

Regarding (2), having an AOSP source checkout is invaluable...

        http://source.android.com/source/index.html

JCERSACipher.java:450 leads us ~here: http://www.docjar.org/html/api/org/bouncycastle/jce/provider/JCERSACipher.java.html#441

          449                   throw new ArrayIndexOutOfBoundsException("too much data for RSA block");

(What's a line or ten among friends and URI anchors?)

In short, I don't think you can pass the entire buffer to DoFinal(byte[]) as you're doing in CustomRSA.DecryptValue() and CustomRSA.EncryptValue(). I believe you'll need to do it "piecewise", presumably with (repeated?) calls to Cipher.Update(byte[]):

        http://developer.android.com/reference/javax/crypto/Cipher.html#update(byte[])

Alternatively (relatedly?):

1. The exception could be due to the use of the wrong API: .NET DecryptValue() is a "raw" (unpadded) decryption (which is what is required here since SSL has it's own padding
requirements), while Decrypt expect a padded input;

2. Endianness: some BigInteger library reverse the byte stream

> I am not really sure why the Mono.Security.Cryptography.PKCS1.Sign_v15 method calls the CustomRSA.DecryptValue method - I would expect that signing would involve encryption of bytes, not their decryption.

Quoting my SSL expert:

The fact that DecryptValue is called is normal; it's a bit misleading but RSA is foremost used for encryption so those name are used in the API, e.g.

Encryption: anybody (public) can encrypt something only you (private) can decrypt.

So the API is named:
* Encrypt: when using the public key
* Decrypt: when using the private key

Signature: only you (private) can sign a document that anybody (public) can verify.

Reusing the same API you find yourself using Decrypt to sign :-)

In short, it looks like your RSA class is correct, semantically, it's the implementation that is not (as Java is complaining about how you're calling it).

 - Jon

_______________________________________________
Monodroid mailing list
[hidden email]

UNSUBSCRIBE INFORMATION:
http://lists.ximian.com/mailman/listinfo/monodroid