Deferred TLS/SSL handshakes on windows mobile

I recently received an email and blog comment from João Almeida, who was trying to use the SslHelper class to make a secure connection to an SMTP server and was having some trouble. After doing a little research I found this is a little different from doing an HTTPS connection. The SMTP protocol requires the connection to start in plain text and the TLS handshake to happen after the STARTTLS command is issued. Here’s how a typical secure SMTP session would start (bolded text is what the SMTP client needs to send, normal text are server responses):

 

>telnet smtp.live.com 587

220 BLU0-SMTP8.blu0.hotmail.com Microsoft ESMTP MAIL Service, Version: 6.0.3790.3959 ready at  Mon, 14 Sep 2009 16:44:33 -0700
EHLO
250-BLU0-SMTP8.blu0.hotmail.com Hello [131.107.0.75]
250-TURN
250-SIZE 35840000
250-ETRN
250-PIPELINING
250-DSN
250-ENHANCEDSTATUSCODES
250-8bitmime
250-BINARYMIME
250-CHUNKING
250-VRFY
250-TLS
250-STARTTLS
250 OK
STARTTLS
220 2.0.0 SMTP server ready

Up to this point everything needs to be in plain text. But once the 220 status code is received the TLS handshake needs to be started.

So how does the SslHelper code need to change to accommodate this? We need to ask winsock to do a deferred handshake as follows (please refer to the full listing of SslHelper). First, we need to bring in a couple of constants from the mobile SDK:

private const int SSL_FLAG_DEFER_HANDSHAKE = 0x0008;

private const int _SO_SSL_PERFORM_HANDSHAKE = 0x0d;

private const long SO_SSL_PERFORM_HANDSHAKE = _SO_SSL | _SO_SSL_PERFORM_HANDSHAKE;

Next, we need to tell the socket that we want it to use a deferred handshake by calling socket.IOControl with the SSL_FLAG_DEFER_HANDSHAKE. We do this in the constructor after setting the ssl cert validation hook:

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

if (deferHandshake)

    socket.IOControl((int)SO_SSL_SET_FLAGS, BitConverter.GetBytes(SSL_FLAG_DEFER_HANDSHAKE), null);

Finally, we need to add a method that will let us start the handshake when we need to:

        public void DoHandshake()

        {

            socket.IOControl((int)SO_SSL_PERFORM_HANDSHAKE, BitConverter.GetBytes(ptrHost.ToInt32()), null);

        }

With this in place, here’s a quick and dirty example of how to start a TLS session with an SMTP server:

string SmtpServer = "smtp.live.com";

int port = 587;

IPHostEntry IPhst = Dns.GetHostEntry(SmtpServer);

IPEndPoint endPt = new IPEndPoint(IPhst.AddressList[0], port);

Socket s = new Socket(endPt.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

using (SslHelper helper = new SslHelper(s, SmtpServer, true) )

{

    string line = null;

    s.Connect(endPt);

    StreamReader sr = new StreamReader(new NetworkStream(s), Encoding.ASCII);                   

    s.Send(Encoding.ASCII.GetBytes("EHLO\r\n"));

    while ((line = sr.ReadLine()) != "250 OK") ;

    s.Send(Encoding.ASCII.GetBytes("STARTTLS\r\n"));

    while (!(line = sr.ReadLine()).StartsWith("220")) ;

    helper.DoHandshake();

    Debug.WriteLine("success");

}

Again, thanks to João for the question and for helping me validate the approach.