Andy
Andy

Reputation: 97

How can I support SSL and non-SSL traffic on the same port using TIdHTTPServer and OpenSSL?

I'm trying to set up a web server in Delphi XE3 using Indy and OpenSSL that can serve traffic over both HTTP and HTTPS connections on the same port.

I have seen two main approaches to this, and neither seems to work for me.

First: Up-front TLS/SSL. This involves reading the first few bytes of the stream to look for the "Client-Hello" part of the non-secure handshake and (if found) invoking the server SSL handshake response, but if I do that, the OpenSSL library does not recognize the handshake because I've stripped off the leading bytes of the message.

Second: TLS after STARTTLS (or equivalent). This involves sending a special set of characters (STARTTLS) which will be immediately followed by "Client-Hello". The server then leaves the entire SSL handshake message intact to pass to the OpenSSL library. The problem with this approach is that most web browsers don't support it (RFC 2817).

For a summary of the two approaches, look here: What happens on the wire when a TLS / LDAP or TLS / HTTP connection is set up?)

How can I support SSL and non-SSL traffic on the same port using TIdHTTPServer and OpenSSL?

Upvotes: 5

Views: 2073

Answers (2)

Sartaj
Sartaj

Reputation: 169

Use Indy lib to create Https Server with class TIdHTTPServer. bind your request handler. Unit you need to compile:- IdStackConsts, IdStackBSDBase, IdStack,IdWinsock2

    // Assign request handler
   Server.OnCommandGet := @HandleRequest;
   Server.OnConnect := @OnConnect;
   Server.OnQuerySSLPort:= @HandleSslQuery;     

Disable SSL on all connection

    procedure TShttpsServer.HandleSslQuery(APort: TIdPort; var VUseSSL: Boolean);
begin
   // disable SSL with false and enable on connection with check if https request
   VUseSSL := False;
   APort := numPort;
end;     

Now Check connection type Http or Https:-

procedure TShttpsServer.OnConnect(AContext: TIdContext);
begin
  // check connection is https or http
  blnHttps := SslTlsHandshakeDetected(AContext.Binding.Handle);
  // enable https if connection is https
  TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough := not blnHttps;

end; 

// adapted from http://cboard.cprogramming.com/networking-device-communication/166336-detecting-ssl-tls-client-handshake.html
function TShttpsServer.SslTlsHandshakeDetected(ASocket: TIdStackSocketHandle): Boolean;
var
  Buffer: array[0..5] of Byte;
  NumBytes, Len: Integer;
  MsgType: Byte;
begin
  Result := False;

  // sniff the first few bytes from the socket directly, do not remove them from
  // its inbound buffer, in case the SSLIOHandler needs to read them again later ...
  NumBytes := GStack.CheckForSocketError(TIdStackBSDBaseAccess(GBSDStack).WSRecv(ASocket, Buffer, SizeOf(Buffer), MSG_PEEK));

  // client disconnected?
  if NumBytes = 0 then Exit;

  if NumBytes < 3 then Exit;

  // SSL v3 or TLS v1.x ?

  if (Buffer[0] = $16) and // type (22 = handshake)
    (Buffer[1] = $3) then // protocol major version (3.0 = SSL v3, 3.x = TLS v1.x)
  begin
    Result := True;
    Exit;
  end;

  // SSL v2 ?

  if (Buffer[0] and $80) <> 0 then
  begin
    // no padding, 2-byte header
    Len := (Integer(buffer[0] and $7F) shl 8) + buffer[1];
    MsgType := buffer[2];
  end else
  begin
    // padding, 3-byte header
    Len := (Integer(buffer[0] and $3F) shl 8) + buffer[1];
    MsgType := Buffer[3];
  end;

  Result := (Len > 9) and
               (MsgType = $1); // msg type (1 = client hello)
end;
                           

Now you have Https and Http server on same port, don't use standard ports

Upvotes: 0

Remy Lebeau
Remy Lebeau

Reputation: 595377

What you ask for is not possible with Indy out of the box. Indy's default SSL implementation uses OpenSSL's traditional API, where it does all of its own socket I/O, so it needs direct access to the complete handshake data without letting you peek at the data first. However, all is not lost. You have a couple of choices:

1) use libpcap/Winpcap to capture and look at the first few bytes of raw data coming off the wire for new connections before the socket provides it to your application.

2) write your own TIdIOHandler derived class that uses OpenSSL's newer BIO API, or Microsoft's SChannel API, so you are in control of the socket I/O and can read incoming bytes yourself and look at them before pushing them into the encryption engine for processing.

UPDATE: there is actually a 3rd option, using Indy's default classes:

3) in the TIdHTTPServer.OnQuerySSLPort event, set the VUseSSL parameter to False for all requests, thus turning off TIdHTTPServer's automatic handling of the SSL/TLS handshake. Then, in the TIdHTTPServer.OnConnect event (which is fired after the OnQuerySSLPort event), you can peek the first few bytes directly from the socket connection (using the OS's socket API recv() function with the AContext.Binding.Handle socket handle and the MSG_PEEK flag). If the peeked bytes represent the beginning of a "Client-Hello" SSL/TLS message, then type-cast the AContext.Connection.IOHandler object to TIdSSLIOHandlerSocketBase and set its PassThrough property to False to initiate OpenSSL's normal SSL/TLS handshake processing. Since the peeked bytes will still be in the socket's internal buffer, OpenSSL will be able to read them.

Upvotes: 1

Related Questions