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.
Comments
Anonymous
October 05, 2009
Hi , Thanks for posting this code. I am trying to establish FTP - SSL connection. I am able to create a socket ,but when I am trying to create a data socket I am having problems. Like after I create a datasocket , the ftp server just not respond or throw any exception. I hope the problem lies some where in creating datasocket - Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); IPEndPoint ep = new IPEndPoint(Dns.GetHostEntry(remoteHost).AddressList[0], port); if (useSSL) { var sslHelp = new CommunicationsEngine.SSLHelper(s, remoteHost,false); try { s.Connect(ep); } catch (Exception exp) { cleanup(); throw new IOException("Couldn't Connect SSL Data Socket " + exp.Message); } if (s.Connected) { readReply(); } if (retValue != 220) { cleanup(); throw new IOException(reply.Substring(4)); } can you please give me your comments?Anonymous
October 06, 2009
The comment has been removedAnonymous
April 20, 2010
Bindu,Your code:Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);You should be using ProtocolType.TCP instead of ProtocolType.IP.Kind regards,Matthias VanceAnonymous
March 08, 2012
Thanks, Carlos! It works! I've done FTP Client with SslHelper. Like SMTP, FTP, at the beginning, ask for plain text, after set AUTH SSL, socket must be changed.Resuming code:private string remoteHost = "";privete int remotePort = 21;private string remoteUser = "";private string remotePass = "";private SslHelper sslHelper;private Socket clientSocket;int retValue=0;private string reply = "";(...) private void button1_Click(object sender, EventArgs e) { remoteHost = "192.168.2.106";//Start with Plain Text: loginWithoutUser(); string cmd = "AUTH SSL"; sendCommand(cmd);//change to ssl Socket getSslStream(); //Login FTP Secure remoteUser = "root"; remotePass = "1234"; login(); ftp.close(); } public void login() { try { if (clientSocket == null || clientSocket.Connected == false) { this.loginWithoutUser(); } } catch (Exception) { throw new IOException("Couldn't connect to remote server"); } sendCommand("USER " + remoteUser); if (!(retValue == 331 || retValue == 230)) { throw new IOException(reply.Substring(4)); } if (retValue != 230) { sendCommand("PASS " + remotePass); if (!(retValue == 230 || retValue == 202)) { throw new IOException(reply.Substring(4)); } } } /// /// Login to the remote server. /// public void loginWithoutUser() { clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPHostEntry IpToDomainName = Dns.GetHostEntry(remoteHost); IPEndPoint ep = new IPEndPoint(IpToDomainName.AddressList[0], remotePort); using (sslHelper = new SslHelper(clientSocket, IpToDomainName.HostName)) { try { clientSocket.Connect(ep); } catch (Exception) { throw new IOException("Couldn't connect to remote server"); } } readReply(); if (retValue != 220) { close(); throw new IOException(reply.Substring(4)); } } public void getSslStream() { this.getSslStream(clientSocket); } public void getSslStream(Socket Csocket) { sslHelper.DoHandshake(); stream = new NetworkStream(Csocket); } public void sendCommand(String command) { clientSocket.Send(cmdBytes, cmdBytes.Length, 0); readReply(); } private void readReply() { reply = readLine(); retValue = Int32.Parse(reply.Substring(0, 3)); } private string readLine() { string mes = ""; while (true) { bytes = clientSocket.Receive(buffer, buffer.Length, 0); mes += ASCII.GetString(buffer, 0, bytes); if (bytes < buffer.Length) { break; } } char[] seperator = { 'n' }; string[] mess = mes.Split(seperator); if (mes.Length > 2) { mes = mess[mess.Length - 2]; } else { mes = mess[0]; } if (!mes.Substring(3, 1).Equals(" ")) { return readLine(); } return mes; }Anonymous
October 17, 2013
Hi Fabian, I've a problem with your example. public void getSslStream(Socket Csocket) { sslHelper.DoHandshake(); stream = new NetworkStream(Csocket); }What is "stream", where is defined? Have you a complete example?Alessandro