Udostępnij za pośrednictwem


Enable SSL for managed socket on windows mobile

I recently had to implement an application on windows mobile that would talk to a service via an SSL socket. This wasn’t just doing https requests so HttpWebRequest was not an option. And unfortunately the compact framework doesn’t include the SslStream class (which would have made my life easier).

I found windows mobile sockets support SSL through winsock so I decided to explore this route. Since my application was mostly managed it took a little interop dancing but I finally got it to work. What I came up with is a helper class that can take an existing socket and will enable SSL on it. For example:

 

var socket = new Socket(ipaddr, SocketType.Stream, ProtocolType.IP);

using (var sslHelper = new SslHelper(socket, host))

{

socket.Connect(ep);

     //at this point the SSL handshake has occurred and you can

    //send and receive normally

}

 

Below is the code for this class. This is not meant to be production ready but can serve as an illustration on how to hook things up. In my case this was used as part of a proof of concept prototype.

It took me some time to figure out how all the pieces work together since the documentation is a little scattered but hopefully if you have to do something similar it can save you some time.

Another option might be to use SSPI, although I haven’t looked into that yet.

 

using System;

using System.Diagnostics;

using System.Net.Sockets;

using System.Runtime.InteropServices;

using System.Security.Cryptography;

using System.Security.Cryptography.X509Certificates;

using System.Text;

using System.Globalization;

 

namespace SslTest

{

    class SslHelper : IDisposable

    {

        private const ushort SO_SECURE = 0x2001;

        private const ushort SO_SEC_SSL = 0x2004;

        private const int _SO_SSL_FLAGS = 0x02;

        private const int _SO_SSL_VALIDATE_CERT_HOOK = 0x08;

        private const int SO_SSL_FAMILY = 0x00730000;

        private const long _SO_SSL = ((2L << 27) | SO_SSL_FAMILY);

        private const uint IOC_IN = 0x80000000;

        private const long SO_SSL_SET_VALIDATE_CERT_HOOK = (IOC_IN | _SO_SSL | _SO_SSL_VALIDATE_CERT_HOOK);

        private const long SO_SSL_SET_FLAGS = (IOC_IN | _SO_SSL | _SO_SSL_FLAGS);

 

        private const int SSL_CERT_X59            = 1;

        private const int SSL_ERR_OKAY            = 0;

        private const int SSL_ERR_FAILED          = 2;

        private const int SSL_ERR_BAD_LEN         = 3;

        private const int SSL_ERR_BAD_TYPE        = 4;

        private const int SSL_ERR_BAD_DATA        = 5;

        private const int SSL_ERR_NO_CERT         = 6;

        private const int SSL_ERR_BAD_SIG         = 7;

        private const int SSL_ERR_CERT_EXPIRED    = 8;

        private const int SSL_ERR_CERT_REVOKED    = 9;

        private const int SSL_ERR_CERT_UNKNOWN    = 10;

        private const int SSL_ERR_SIGNATURE       = 11;

        private const int SSL_CERT_FLAG_ISSUER_UNKNOWN = 0x0001;

 

 

        public delegate int SSLVALIDATECERTFUNC(uint dwType, IntPtr pvArg, uint dwChainLen, IntPtr pCertChain, uint dwFlags);       

        private IntPtr ptrHost;

        private IntPtr hookFunc;

 

        public SslHelper(Socket socket, string host)

        {

            //The managed SocketOptionName enum doesn't have SO_SECURE so here we cast the integer value

            socket.SetSocketOption(SocketOptionLevel.Socket, (SocketOptionName)SO_SECURE, SO_SEC_SSL);

 

            //We need to pass a function pointer and a pointer to a string containing the host

            //to unmanaged code

            hookFunc = Marshal.GetFunctionPointerForDelegate(new SSLVALIDATECERTFUNC(ValidateCert));

 

            //Allocate the buffer for the string

            ptrHost = Marshal.AllocHGlobal(host.Length + 1);

            WriteASCIIString(ptrHost, host);

 

            //Now put both pointers into a byte[]

            var inBuffer = new byte[8];

            var hookFuncBytes = BitConverter.GetBytes(hookFunc.ToInt32());

            var hostPtrBytes = BitConverter.GetBytes(ptrHost.ToInt32());

            Array.Copy(hookFuncBytes, inBuffer, hookFuncBytes.Length);

            Array.Copy(hostPtrBytes, 0, inBuffer, hookFuncBytes.Length, hostPtrBytes.Length);

 

            unchecked

            {

                socket.IOControl((int)SO_SSL_SET_VALIDATE_CERT_HOOK, inBuffer, null);

            }

        }

 

        private static void WriteASCIIString(IntPtr basePtr, string s)

        {

            byte[] bytes = Encoding.ASCII.GetBytes(s);

            for (int i = 0; i < bytes.Length; i++)

                Marshal.WriteByte(basePtr, i, bytes[i]);

 

            //null terminate the string

            Marshal.WriteByte(basePtr, bytes.Length, 0);

        }

 

        #region IDisposable Members

 

        ~SslHelper()

        {

            ReleaseHostPointer();

        }

 

        public void Dispose()

        {

            GC.SuppressFinalize(this);

            ReleaseHostPointer();

        }

 

        private void ReleaseHostPointer()

        {

            if (ptrHost != IntPtr.Zero)

            {

                Marshal.FreeHGlobal(ptrHost);

                ptrHost = IntPtr.Zero;

            }

        }

 

        #endregion

 

        private int ValidateCert(uint dwType, IntPtr pvArg, uint dwChainLen, IntPtr pCertChain, uint dwFlags)

        { 

            //According to https://msdn.microsoft.com/en-us/library/ms940451.aspx:

            //

            //- dwChainLen is always 1

            //- Windows CE performs the cert chain validation

            //- pvArg is the context data we passed into the SO_SSL_SET_VALIDATE_CERT_HOOK call so in our

            //- case is the host name

            //

            //So here we are responsible for validating the dates on the certificate and the CN

 

 

            if (dwType != SSL_CERT_X59)

                return SSL_ERR_BAD_TYPE;

 

            //When in debug mode let self-signed certificates through ...

#if !DEBUG

            if ((dwFlags & SSL_CERT_FLAG_ISSUER_UNKNOWN) != 0)

                return SSL_ERR_CERT_UNKNOWN;

#endif

 

            Debug.Assert(dwChainLen == 1);

 

            //Note about the note: an unmanaged long is 32 bits, unlike a managed long which is 64. I was missing

            //this fact when I wrote the comment. So the docs are accurate.

            //NOTE: The documentation says pCertChain is a pointer to a LPBLOB struct:

            //

            // {ulong size, byte* data}

            //

            //in reality the size is a 32 bit integer (not 64).

            int certSize = Marshal.ReadInt32(pCertChain);

            IntPtr pData = Marshal.ReadIntPtr(new IntPtr(pCertChain.ToInt32() + sizeof(int)));

 

            byte[] certData = new byte[certSize];

 

            for (int i = 0; i < certSize; i++)

                certData[i] = Marshal.ReadByte(pData, (int)i);

 

            X509Certificate2 cert;

            try

            {

                cert = new X509Certificate2(certData);

            }

            catch (ArgumentException) { return SSL_ERR_BAD_DATA; }

            catch (CryptographicException) { return SSL_ERR_BAD_DATA; }

 

            //Validate the expiration date

            if (DateTime.Now > DateTime.Parse(cert.GetExpirationDateString(), CultureInfo.CurrentCulture))

                return SSL_ERR_CERT_EXPIRED;

 

            //Validate the effective date

            if (DateTime.Now < DateTime.Parse(cert.GetEffectiveDateString(), CultureInfo.CurrentCulture))

                return SSL_ERR_FAILED;

 

            string certName = cert.GetName();

            Debug.WriteLine(certName);

 

            //Validate the CN

            string host = ReadAnsiString(pvArg);

            if (!certName.Contains("CN=" + host))

                return SSL_ERR_FAILED;

 

            return SSL_ERR_OKAY;

        }

 

        private static string ReadAnsiString(IntPtr pvArg)

        {

            byte[] buffer = new byte[1024];

            int j = 0;

            do

            {

                buffer[j] = Marshal.ReadByte(pvArg, j);

                j++;

            } while (buffer[j - 1] != 0);

            string host = Encoding.ASCII.GetString(buffer, 0, j - 1);

            return host;

        }

    }

}

Comments

  • Anonymous
    September 11, 2009
    Hi, is this class able to work with TLS?

  • Anonymous
    September 12, 2009
    Yes. The code currently lets winsock select the protocol. To use a specific protocol, you need to follow the steps described in:http://msdn.microsoft.com/en-us/library/aa916117.aspxAlthough from managed code you would call socket.IOControl instead of WSAIoctl.

  • Anonymous
    October 09, 2009
    The comment has been removed

  • Anonymous
    November 25, 2009
    Kuki, you are write. In CFW 2.0 this code is not working at all, however in CFW 3.5 it does work. Connection is establishing validating callback fired, however comes another problem: error 10036. The receiving part working without problems. I am getting the error only during sending. During about each third sending operation. Blocking property does not help. Blocking sending and receiving by SyncLock does not help either. Has somebody any clue?  

  • Anonymous
    January 12, 2010
    Hi , Will this approach work in craeting a socket enabled for SSL in windows server environment(2008)

  • Anonymous
    January 13, 2010
    Creating an SSL enabled socket on windows server is much easier because you have the full .NET framework at our disposal (as opposed to the compact framework on windows mobile). Look at the SslStream class in System.Net.Security.

  • Anonymous
    February 18, 2010
    Hi, thanks for the article, very interesting. Reading data is fine, but I have a problem where sending data is unreliable, often it will produce an exception when calling write on the networkStream "Unable to write data to the transport connection." with the inner exception "A blocking operation is currently executing". We haven't been able to find a solution to this. Also, it didn't work at all on .NET CF 2, which doesn't matter to me but may be some sort of clue as to the problem. Any ideas? thanks

  • Anonymous
    February 19, 2010
    Hello Martin,I haven't tried this but it seems like you need to put your socket in non-blocking mode using socket.IOControl. You may also want to look into IOControlCode.AsyncIO if you are doing things asynchronously.See: http://msdn.microsoft.com/en-us/library/system.net.sockets.iocontrolcode.aspxCarlos

  • Anonymous
    March 02, 2010
    The comment has been removed

  • Anonymous
    April 09, 2010
    I haven't tested this but I think that you should do following to be able sending. Make sure that you are not SENDING and RECEIVIN in the same time. Thant means receive data olny after successfull send. The best way (I think) is to use lock(onstaticobjec){ receive or send function call}. Because Windows CE doesn't have native support for DUPLEX sockets. Even if you use C++ for socket you will not be able to send and receive at the same time.

  • Anonymous
    April 20, 2010
    I am hoping you can explain a bit more on how to implement this for listening sockets.I really don't want to use the SSLStream class, since that affects performance (I want to use "raw" sockets).Kind regards,Matthias Vance

  • Anonymous
    April 20, 2010
    Thank you for the interesting article! (I forgot to add this to my other comment)

  • Anonymous
    February 02, 2011
    Hi,I have tried this implementation and it seems to be working just fine as long as there is low network traffic.Part of my application is sending data over ssl sockets (it basicly sends message and receive response). This messages are being sent every second, sometimes faster and on this device there is also a webserver. Problem usually rises when the user tries to load the web page from this device. Almost all the time application crashes down with error that looks like this:Exception 'Raised Exception' (-1): Thread-Id=0430002e(pth=81bec8cc), Proc-Id=041b0022(pprc=816f751c) 'CAT12CE.exe', VM-active=041b0022(pprc=816f751c) 'CAT12CE.exe'PC=4005d180(coredll.dll+0x0004d180) RA=8007d4c8(kernel.dll+0x000064c8) SP=00def81c, BVA=00def8c8Exception 'Raised Exception' (-1): Thread-Id=0430002e(pth=81bec8cc), Proc-Id=041b0022(pprc=816f751c) 'CAT12CE.exe', VM-active=041b0022(pprc=816f751c) 'CAT12CE.exe'PC=40020260(coredll.dll+0x00010260) RA=8007d4c8(kernel.dll+0x000064c8) SP=00def228, BVA=00000000Any idea?ThanksDaniel Ray

  • Anonymous
    December 14, 2011
    Hi, is this code useful for a secure FTP??? Im receiving the SocketException with ErrorCode 10022.Kind Regards

  • Anonymous
    December 15, 2011
    Hi, is this code useful for a secure FTP??? Im receiving the SocketException with ErrorCode 10022 when I try to connect with Socket.Connect method.Kind Regards

  • Anonymous
    March 28, 2012
    The comment has been removed

  • Anonymous
    May 01, 2012
    How to send client certificate using ssl helper for windows mobile?

  • Anonymous
    May 01, 2012
    How to send client certificate using sslhelper for windows mobile?

  • Anonymous
    May 01, 2012
    Yes Mr. Bala M, I am also expecting help on the same issue.If you get any help on this, please feel free to send me the same.

  • Anonymous
    July 19, 2012
    Any Idea how can I select Cipher Suites/algorithm in .NET CF or C++ ? I want to select AES 256 cipher algorithm for my Secure Socket.