{==============================================================================| | 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.