Getting a managed socket to talk SSL
This is a follow up to my previous post, where I showed a helper class I wrote to enable a managed socket to communicate via SSL on windows mobile. It was my first technical blog post and a friend gave me some very good feedback: blogs can be more interesting if they not only show the solution to a problem, but also how the solution was found. So here I’d like to tell you a little bit of what I tried before finally making SSL work on my mobile device.
I started searching MSDN and came across this article. The article talks about a native implementation using winsock and basically talks about calling setsockopt and WSAIoctl on the socket to enable SSL and register a certificate validation callback, respectively. I found the managed Socket class has corresponding methods: SetSocketOption and IOControl and at this point I thought this would be straightforward. To a certain extent it was, but I still had some work to do.
My first surprise was that SetSocketOption takes a SocketOptionName enum value as the second parameter, but this enum doesn’t have the equivalent of SO_SECURE. However, C# was nice enough to let me cast an arbitrary integer value to the enum I needed:
//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);
Next in line was calling socket.IOControl to register a certificate validation callback. Turns out this method is a very general purpose one that takes a control code and then simply a buffer whose interpretation depends on such code. For SO_SSL_SET_VALIDATE_CERT_HOOK the first four bytes in this buffer are a function pointer to the certificate validation callback and the next 4 bytes are a pointer to a string containing the name of the host. Since we want to implement the validation callback in C# (more on why later) we need to first get a pointer to a delegate that we can pass to unmanaged code:
hookFunc = Marshal.GetFunctionPointerForDelegate(new SSLVALIDATECERTFUNC(ValidateCert));
Note that it is important to maintain a reference to this IntPtr, otherwise the GC could collect it while the unmanaged code still holds a reference to it. I missed this detail the first time around and it seemed to work fine. But this was just because the GC was not reclaiming that pointer right away and that could change for any number of reasons so I was risking running into a very hard to find bug later on.
Now we need to write the host name into an unmanaged buffer, which we first need to allocate:
//Allocate the buffer for the string
ptrHost = Marshal.AllocHGlobal(host.Length + 1);
WriteASCIIString(ptrHost, host);
This buffer is freed when the object is disposed since it needs to stick around until the certificate validation happens. Finally, we put these 8 bytes into a buffer and call socket.IOControl:
//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);
}
That’s it as far as enabling SSL over the socket. All that remains is writing the ValidateCert function. This turns out to be relatively simple because .NET has a X509Certificate2 class that can parse the certificate blob passed to us. You can see the implementation of this function in the previous post, where I do the required validations. But I want to make a comment about a particular piece of code that had me scratch my head for a little while. This is the code that takes the unmanaged pointer to the certificate passed in and reads the data into managed code:
//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];
When I read in the documentation that size was a long, I was thinking C#, so I was trying to read the size as a 64 bit integer which gave back a huge number. Needles to say the allocation of the byte[] threw an OutOfMemoryException. What I was missing was the fact that while a C# long is 64 bits, a C++ long is only 32 bits. And now that I think about it, it wasn’t just the C++ side of my brain that was turned off at that point, but also my common sense. Why on earth would you need 64 bits to indicate the size of the certificate? :)
Lucky for me I decided to just try reading 32 bits instead and then it all worked. However, I came to the wrong conclusion that the docs were mistaken when in fact I was failing to think about the differences in data types across managed and unmanaged. It wasn’t until I was re-reading the code for this post and I realized the mistake I had made.
Here are links to some of the material I read when putting this together:
https://msdn.microsoft.com/en-us/library/system.net.sockets.socket_members.aspx
https://msdn.microsoft.com/en-us/library/aa916117.aspx
https://windowsmobilepro.blogspot.com/2006/03/windows-mobile-secure-socket_25.html
Comments
Anonymous
August 27, 2009
Good article and explanation. Good catch on the GC. The links are very helpful too. If I was an official member of DotNetKicks, I would say "you've been kicked". And that is a good thing.Anonymous
September 06, 2009
Neat one, you helped me a lot, thank youAnonymous
September 09, 2009
Great! I'm glad to hear it helped.Anonymous
October 11, 2009
Great article. I have one question why it fails to work under netcf 2.0 ? Everything works on on 3.5 but when i try to run it under 2.0 i got native exceptions. Some other behaviour in Socket class implementation?Anonymous
November 16, 2009
It is interesting technique. However by trying your code I came the same issues as Kuki did. I have cfw 2.0. Never got validation callback. Server self working fine (tested with another XP based ssl client). I am interested what was answers to Kuki's questions. Kind regardsAnonymous
May 06, 2010
I need to implement a secure TCP Server under .Net CF on windows CE. Do you have any examples.Anonymous
September 10, 2010
Great article. I was able to get an SSL socket working on C#.NET Compact Framework version 3.5. With this setup I can write data, then read data, then write data, etc. Unfortunately I need to go one step further - I need a duplex setup where I can be listening for incoming data while writing outgoing data to the socket. With the SSLHelper class, the underlying socket will block on all reads, including BeginRead(). Without the ability to set a socket timeout (not available in the CF), any read will lock until data comes, thus blocking the ability to write data while listening for incoming data.Has anyone solve this problem?Anonymous
July 14, 2011
Hello,Almost a year later ... :)I am forced to develop against .Net CF 2.0, Did anyone find a fix for the problem?Anonymous
March 29, 2012
How can we use it with windows application. It is throwing error in windows application.Anonymous
March 29, 2012
how can we use it in windows application.Anonymous
April 01, 2012
On the desktop you don't need this helper because you have access to the SslStream class:msdn.microsoft.com/.../system.net.security.sslstream.aspxI wrote this helper because this class is not available on .NET Compact FrameworkAnonymous
June 06, 2012
while using sslhelper server sending response 403 forbidden.in the server Required SSL checked and client certificates required.if server Required SSL checked and client certificates accept it's getting response properly.how we will get response when server Required SSL checked and client certificates required.can we attach certificate with socket like httpwebrequest.Anonymous
June 06, 2012
how to send client certificate using ssl helper for windows mobile?