{==============================================================================|
| Project : Ararat Synapse                                       | 001.000.006 |
|==============================================================================|
| Content: SSL support for SecureBlackBox 10+                                  |
|==============================================================================|
| Copyright (c)1999-2005, Lukas Gebauer                                        |
| All rights reserved.                                                         |
|                                                                              |
| Redistribution and use in source and binary forms, with or without           |
| modification, are permitted provided that the following conditions are met:  |
|                                                                              |
| Redistributions of source code must retain the above copyright notice, this  |
| list of conditions and the following disclaimer.                             |
|                                                                              |
| Redistributions in binary form must reproduce the above copyright notice,    |
| this list of conditions and the following disclaimer in the documentation    |
| and/or other materials provided with the distribution.                       |
|                                                                              |
| Neither the name of Lukas Gebauer nor the names of its contributors may      |
| be used to endorse or promote products derived from this software without    |
| specific prior written permission.                                           |
|                                                                              |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"  |
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE    |
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE   |
| ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR  |
| ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL       |
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR   |
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER   |
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT           |
| LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY    |
| OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH  |
| DAMAGE.                                                                      |
|==============================================================================|
| The Initial Developer of the Original Code is Lukas Gebauer (Czech Republic).|
| Portions created by Lukas Gebauer are Copyright (c)2005.                     |
| All Rights Reserved.                                                         |
|==============================================================================|
| Contributor(s):                                                              |
|   Allen Drennan (adrennan@wiredred.com)                                      |
|==============================================================================|
| History: see HISTORY.HTM from distribution package                           |
|          (Found at URL: http://www.ararat.cz/synapse/)                       |
|==============================================================================}

{:@abstract(SSL plugin for Eldos SecureBlackBox)

For handling keys and certificates you can use this properties:
@link(TCustomSSL.CertCAFile), @link(TCustomSSL.CertCA),
@link(TCustomSSL.TrustCertificateFile), @link(TCustomSSL.TrustCertificate),
@link(TCustomSSL.PrivateKeyFile), @link(TCustomSSL.PrivateKey),
@link(TCustomSSL.CertificateFile), @link(TCustomSSL.Certificate),
@link(TCustomSSL.PFXFile). For usage of this properties and for possible formats
of keys and certificates refer to SecureBlackBox documentation.
}

{$IFDEF FPC}
  {$MODE DELPHI}
{$ENDIF}
{$H+}

unit ssl_sbb;

interface

uses
  SysUtils, Classes, Windows, blcksock, synsock, synautil, synacode,
  SBSSLClient, SBSSLServer, SBSSLConstants, SBX509, SBWinCertStorage,
  SBCustomCertStorage, SBTypes, SBUtils, SBConstants, SBSessionPool;

type
  {:@abstract(class implementing SecureBlackbox SSL plugin.)
   Instance of this class will be created for each @link(TTCPBlockSocket).
   You not need to create instance of this class, all is done by Synapse itself!}
  TSSLSBB = class(TCustomSSL)
  protected
    FServer: Boolean;
    FElSecureClient: TElSSLClient;
    FElSecureServer: TElSSLServer;
    FElCertStorage: TElMemoryCertStorage;
    FElX509Certificate: TElX509Certificate;
    FElX509CACertificate: TElX509Certificate;
    FCipherSuites: TBits;
  private
    FAcceptThread: THandle;
    FRecvBuffer: AnsiString;
    FRecvBuffers: AnsiString;
    FRecvDecodedBuffers: AnsiString;
    FNoRecv: Integer;
  private
    function  GetCipherSuite: Integer;
    function  FileToString(const lFile: string): AnsiString;
    function  Prepare(Server: Boolean): Boolean;
    procedure Reset;

    procedure OnCertificateValidate(Sender: TObject; X509Certificate: TElX509Certificate;
      var Validate: TSBBoolean);
    procedure OnData(Sender: TObject; Buffer: Pointer; Size: LongInt);
    procedure OnError(Sender: TObject; ErrorCode: Integer; Fatal: Boolean;
      Remote: Boolean);
    procedure OnReceive(Sender: TObject; Buffer: Pointer; MaxSize: LongInt;
      out Written: LongInt);
    procedure OnSend(Sender: TObject; Buffer: Pointer; Size: LongInt);
  public
    constructor Create(const Value: TTCPBlockSocket); override;
    destructor Destroy; override;
    {:See @inherited}
    function LibVersion: string; override;
    {:See @inherited}
    function LibName: string; override;
    {:See @inherited and @link(ssl_sbb) for more details.}
    function Connect: Boolean; override;
    {:See @inherited and @link(ssl_sbb) for more details.}
    function Accept: Boolean; override;
    {:See @inherited}
    function Shutdown: Boolean; override;
    {:See @inherited}
    function BiShutdown: Boolean; override;
    {:See @inherited}
    function SendBuffer(Buffer: TMemory; Len: Integer): Integer; override;
    {:See @inherited}
    function RecvBuffer(Buffer: TMemory; Len: Integer): Integer; override;
    {:See @inherited}
    function WaitingData: Integer; override;
    {:See @inherited}
    function GetSSLVersion: string; override;
    {:See @inherited}
    function GetPeerSubject: string; override;
    {:See @inherited}
    function GetPeerIssuer: string; override;
    {:See @inherited}
    function GetPeerName: string; override;
    {:See @inherited}
    function GetPeerFingerprint: string; override;
    {:See @inherited}
    function GetCertInfo: string; override;
  published
    property ElSecureClient: TElSSLClient read FElSecureClient write FElSecureClient;
    property ElSecureServer: TElSSLServer read FElSecureServer write FElSecureServer;
    property CipherSuites: TBits read FCipherSuites write FCipherSuites;
    property CipherSuite: Integer read GetCipherSuite;
  end;

implementation

const
  DEFAULT_RECV_BUFFER = 32768;

{ TSSLSBB }

function TSSLSBB.Accept: Boolean;
var
  lResult: Integer;
begin
  Result := False;
  if FSocket.Socket = INVALID_SOCKET then
    Exit;
  if Prepare(True) then
  begin
    FAcceptThread := GetCurrentThreadId;
    FElSecureServer.Open;
    // reset
    FRecvBuffers := '';
    FRecvDecodedBuffers := '';
    // wait for open or error
    while (not FElSecureServer.Active) and
      (FLastError = 0) do
    begin
      // data available?
      if FRecvBuffers <> '' then
      begin
        while FRecvBuffers <> '' do
          FElSecureServer.DataAvailable;
      end
      else
      begin
        // socket recv - ntohs(FSocket.RemoteSin.sin_port)
        lResult := Recv(FSocket.Socket, @FRecvBuffer[1], Length(FRecvBuffer),
          0);
        if lResult = SOCKET_ERROR then
        begin
          FLastErrorDesc := '';
          FLastError := WSAGetLastError;
        end
        else
        begin
          if lResult > 0 then
            FRecvBuffers := FRecvBuffers + Copy(FRecvBuffer, 1, lResult)
          else
            Break;
        end;
      end;
    end;
    if FLastError <> 0 then
      Exit;
    FSSLEnabled := FElSecureServer.Active;
    Result := FSSLEnabled;
  end;
end;

function TSSLSBB.BiShutdown: Boolean;
begin
  Reset;
  Result := True;
end;

function TSSLSBB.Connect: Boolean;
var
  lResult: Integer;
begin
  Result := False;
  if FSocket.Socket = INVALID_SOCKET then
    Exit;
  if Prepare(False) then
  begin
    FElSecureClient.Open;
    // reset
    FRecvBuffers := '';
    FRecvDecodedBuffers := '';
    // wait for open or error
    while (not FElSecureClient.Active) and
      (FLastError = 0) do
    begin
      // data available?
      if FRecvBuffers <> '' then
      begin
        while FRecvBuffers <> '' do
          FElSecureClient.DataAvailable;
      end
      else
      begin
        // socket recv
        lResult := synsock.Recv(FSocket.Socket, @FRecvBuffer[1],
          Length(FRecvBuffer), MSG_NOSIGNAL);
        if lResult = SOCKET_ERROR then
        begin
          FLastErrorDesc := '';
          FLastError := WSAGetLastError;
        end
        else
        begin
          if lResult > 0 then
            FRecvBuffers := FRecvBuffers + Copy(FRecvBuffer, 1, lResult)
          else
            Break;
        end;
      end;
    end;
    if FLastError <> 0 then
      Exit;
    FSSLEnabled := FElSecureClient.Active;
    Result := FSSLEnabled;
  end;
end;

{ inherited }
constructor TSSLSBB.Create(const Value: TTCPBlockSocket);
var
  loop1: Integer;
begin
  inherited Create(Value);
  FServer := False;
  FElSecureClient := nil;
  FElSecureServer := nil;
  FElCertStorage := nil;
  FElX509Certificate := nil;
  FElX509CACertificate := nil;
  SetLength(FRecvBuffer, DEFAULT_RECV_BUFFER);
  FRecvBuffers := '';
  FRecvDecodedBuffers := '';
  FNoRecv := 0;
  FCipherSuites := TBits.Create;
  if FCipherSuites <> nil then
  begin
    FCipherSuites.Size := SB_SUITE_LAST + 1;
    // disable exotic suites
    for loop1 := SB_SUITE_FIRST to SB_SUITE_LAST do
      FCipherSuites[loop1] := False;
    // enable common suites
    for loop1 := SB_SUITE_FIRST to SB_SUITE_DH_ANON_DES_SHA_EXPORT do
      FCipherSuites[loop1] := True;
  end;
end;

destructor TSSLSBB.Destroy;
begin
  Reset;
  inherited Destroy;
  if FCipherSuites <> nil then
    FreeAndNil(FCipherSuites);
end;

function TSSLSBB.FileToString(const lFile: string): AnsiString;
var
  lStream: TStream;
begin
  Result := '';
  lStream := TFileStream.Create(lFile, fmOpenRead or fmShareDenyWrite);
  try
    if lStream.Size > 0 then
    begin
      SetLength(Result, lStream.Size);
      lStream.Position := 0;
      lStream.ReadBuffer(PAnsiChar(Result)^, lStream.Size);
    end;
  finally
    lStream.Free;
  end;
end;

function TSSLSBB.GetCertInfo: string;
begin
  Result := '';
  // if FServer then
  // must return a text representation of the ASN of the client certificate
  // else
  // must return a text representation of the ASN of the server certificate
end;

function TSSLSBB.GetCipherSuite: Integer;
begin
  if FServer then
    Result := FElSecureServer.CipherSuite
  else
    Result := FElSecureClient.CipherSuite;
end;

function TSSLSBB.GetPeerFingerprint: string;
begin
  Result := '';
  // if FServer then
  // must return a unique hash string of the client certificate
  // else
  // must return a unique hash string of the server certificate
end;

function TSSLSBB.GetPeerIssuer: string;
begin
  Result := '';
  // if FServer then
  // must return issuer of the client certificate
  // else
  // must return issuer of the server certificate
end;

function TSSLSBB.GetPeerName: string;
begin
  Result := '';
  // if FServer then
  // must return commonname of the client certificate
  // else
  // must return commonname of the server certificate
end;

function TSSLSBB.GetPeerSubject: string;
begin
  Result := '';
  // if FServer then
  // must return subject of the client certificate
  // else
  // must return subject of the server certificate
end;

function TSSLSBB.GetSSLVersion: string;
begin
  Result := 'SSLv3 or TLSv1';
end;

function TSSLSBB.LibName: string;
begin
  Result := 'ssl_sbb';
end;

function TSSLSBB.LibVersion: string;
begin
  Result := 'SecureBlackBox 10+';
end;

// on certificate validate
procedure TSSLSBB.OnCertificateValidate(Sender: TObject;
  X509Certificate: TElX509Certificate; var Validate: TSBBoolean);
begin
  Validate := True;
end;

// on data
procedure TSSLSBB.OnData(Sender: TObject; Buffer: Pointer; Size: LongInt);
var
  lString: AnsiString;
begin
  SetLength(lString, Size);
  Move(Buffer^, lString[1], Size);
  FRecvDecodedBuffers := FRecvDecodedBuffers + lString;
end;

// on error
procedure TSSLSBB.OnError(Sender: TObject; ErrorCode: Integer; Fatal: Boolean;
  Remote: Boolean);
begin
  FLastErrorDesc := '';
  FLastError := ErrorCode;
end;

// on receive
procedure TSSLSBB.OnReceive(Sender: TObject; Buffer: Pointer; MaxSize: LongInt;
  out Written: LongInt);
var
  lLength: Integer;
begin
  lLength := Length(FRecvBuffers);
  if lLength <= MaxSize then
    Written := lLength
  else
    Written := MaxSize;
  Move(FRecvBuffers[1], Buffer^, Written);
  Delete(FRecvBuffers, 1, Written);
end;

// on send
procedure TSSLSBB.OnSend(Sender: TObject; Buffer: Pointer; Size: LongInt);
var
  lResult: Integer;
begin
  if FSocket.Socket = INVALID_SOCKET then
    Exit;
  if Size > 0 then
  begin
    lResult := synsock.Send(FSocket.Socket, Buffer, Size, MSG_NOSIGNAL);
    if lResult = SOCKET_ERROR then
    begin
      FLastErrorDesc := '';
      FLastError := WSAGetLastError;
    end;
  end;
end;

function TSSLSBB.Prepare(Server: Boolean): Boolean;
var
  loop1: Integer;
  lStream: TMemoryStream;
  lCertificate, lPrivateKey, lCertCA: AnsiString;
begin
  Result := False;
  FServer := Server;
  // reset, if necessary
  Reset;
  // init, certificate
  if FCertificateFile <> '' then
    lCertificate := FileToString(FCertificateFile)
  else
    lCertificate := FCertificate;
  if FPrivateKeyFile <> '' then
    lPrivateKey := FileToString(FPrivateKeyFile)
  else
    lPrivateKey := FPrivateKey;
  if FCertCAFile <> '' then
    lCertCA := FileToString(FCertCAFile)
  else
    lCertCA := FCertCA;
  if (lCertificate <> '') and (lPrivateKey <> '') then
  begin
    FElCertStorage := TElMemoryCertStorage.Create(nil);
    if FElCertStorage <> nil then
      FElCertStorage.Clear;
    // apply ca certificate
    if lCertCA <> '' then
    begin
      FElX509CACertificate := TElX509Certificate.Create(nil);
      if FElX509CACertificate <> nil then
      begin
        with FElX509CACertificate do
        begin
          lStream := TMemoryStream.Create;
          try
            WriteStrToStream(lStream, lCertCA);
            lStream.Seek(0, soFromBeginning);
            LoadFromStream(lStream);
          finally
            lStream.Free;
          end;
        end;
        if FElCertStorage <> nil then
          FElCertStorage.Add(FElX509CACertificate);
      end;
    end;
    // apply certificate
    FElX509Certificate := TElX509Certificate.Create(nil);
    if FElX509Certificate <> nil then
    begin
      with FElX509Certificate do
      begin
        lStream := TMemoryStream.Create;
        try
          WriteStrToStream(lStream, lCertificate);
          lStream.Seek(0, soFromBeginning);
          LoadFromStream(lStream);
        finally
          lStream.Free;
        end;
        lStream := TMemoryStream.Create;
        try
          WriteStrToStream(lStream, lPrivateKey);
          lStream.Seek(0, soFromBeginning);
          LoadKeyFromStream(lStream);
        finally
          lStream.Free;
        end;
        if FElCertStorage <> nil then
          FElCertStorage.Add(FElX509Certificate);
      end;
    end;
  end;
  // init, as server
  if FServer then
  begin
    FElSecureServer := TElSSLServer.Create(nil);
    if FElSecureServer <> nil then
    begin
      // init, ciphers
      for loop1 := SB_SUITE_FIRST to SB_SUITE_LAST do
        FElSecureServer.CipherSuites[loop1] := FCipherSuites[loop1];
      FElSecureServer.Versions := [sbSSL2, sbSSL3, sbTLS1];
      FElSecureServer.ClientAuthentication := False;
      FElSecureServer.OnError := OnError;
      FElSecureServer.OnSend := OnSend;
      FElSecureServer.OnReceive := OnReceive;
      FElSecureServer.OnData := OnData;
      FElSecureServer.CertStorage := FElCertStorage;
      Result := True;
    end;
  end
  else
    // init, as client
  begin
    FElSecureClient := TElSSLClient.Create(nil);
    if FElSecureClient <> nil then
    begin
      // init, ciphers
      for loop1 := SB_SUITE_FIRST to SB_SUITE_LAST do
        FElSecureClient.CipherSuites[loop1] := FCipherSuites[loop1];
      FElSecureClient.Versions := [sbSSL3, sbTLS1];
      FElSecureClient.OnError := OnError;
      FElSecureClient.OnSend := OnSend;
      FElSecureClient.OnReceive := OnReceive;
      FElSecureClient.OnData := OnData;
      FElSecureClient.OnCertificateValidate := OnCertificateValidate;
      FElSecureClient.CertStorage := FElCertStorage;
      Result := True;
    end;
  end;
end;

function TSSLSBB.RecvBuffer(Buffer: TMemory; Len: Integer): Integer;
var
  lLength: Integer;
begin
  // recv waiting, if necessary
  if FRecvDecodedBuffers = '' then
    WaitingData;
  // received
  lLength := Length(FRecvDecodedBuffers);
  if lLength <= Len then
    Result := lLength
  else
    Result := Len;
  if Result > 0 then
  begin
    Move(FRecvDecodedBuffers[1], Buffer^, Result);
    Delete(FRecvDecodedBuffers, 1, Result);
  end;
end;

procedure TSSLSBB.Reset;
begin
  if FElSecureServer <> nil then
    FreeAndNil(FElSecureServer);
  if FElSecureClient <> nil then
    FreeAndNil(FElSecureClient);
  if FElX509Certificate <> nil then
    FreeAndNil(FElX509Certificate);
  if FElX509CACertificate <> nil then
    FreeAndNil(FElX509CACertificate);
  if FElCertStorage <> nil then
    FreeAndNil(FElCertStorage);
  FSSLEnabled := False;
end;

function TSSLSBB.SendBuffer(Buffer: TMemory; Len: Integer): Integer;
begin
  if FServer then
    FElSecureServer.SendData(Buffer, Len)
  else
    FElSecureClient.SendData(Buffer, Len);
  Result := Len;
end;

function TSSLSBB.Shutdown: Boolean;
begin
  Result := BiShutdown;
end;

function TSSLSBB.WaitingData: Integer;
var
  lResult: Integer;
begin
  Result := 0;
  if FSocket.Socket = INVALID_SOCKET then
    Exit;
  if FRecvBuffers <> '' then
  begin
    while FRecvBuffers <> '' do
      if FServer then
        FElSecureServer.DataAvailable
      else
        FElSecureClient.DataAvailable;
  end
  else
  begin
    // socket recv
    lResult := Recv(FSocket.Socket, @FRecvBuffer[1], Length(FRecvBuffer), 0);
    if lResult = SOCKET_ERROR then
    begin
      FLastErrorDesc := '';
      FLastError := WSAGetLastError;
    end
    else
    begin
      if lResult > 0 then
      begin
        FRecvBuffers := FRecvBuffers + Copy(FRecvBuffer, 1, lResult);
        FNoRecv := 0;
        // data available?
        if FRecvBuffers <> '' then
        begin
          while FRecvBuffers <> '' do
            if FServer then
              FElSecureServer.DataAvailable
            else
              FElSecureClient.DataAvailable;
        end;
      end
      else
        // workaround for dead ssl sockets
        // which can happen if an open succeeds but the connection is abruptly
        // terminated by the client peer
      begin
        if FServer then
        begin
          Inc(FNoRecv);
          if FNoRecv > 25 then
            CloseSocket(FSocket.Socket);
        end;
      end;
    end;
  end;
  // decoded buffers result
  Result := Length(FRecvDecodedBuffers);
end;

{==============================================================================}
initialization
  SSLImplementation := TSSLSBB;

finalization

end.
