{ This file is part of the Free Component Library (FCL) Copyright (c) 1999-2000 by the Free Pascal development team SSL support for ssockets See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} unit sslsockets; {$mode objfpc}{$H+} interface uses Classes, SysUtils, sockets, ssockets, openssl, fpopenssl; Const SSLDataCount = 4; // 0 based. Type TVerifyCertificateEvent = Procedure(Sender : TObject; Allow : Boolean) of object; { TSSLSocketHandler } TSSLSocketHandler = class(TSocketHandler) private FRemoteHostName: String; FSSLLastErrorString: string; FCipherList: string; FVerifyPeerCert: Boolean; FOnVerifyCertificate: TVerifyCertificateEvent; FSSLType: TSSLType; FKeyPassword: string; FUsername: string; FPassword: string; FCertData : Array[0..4] of TSSLData; FSSL: TSSL; FCTX : TSSLContext; FSSLActive : Boolean; FSendHostAsSNI : Boolean; function GetSSLData(AIndex: Integer): TSSLData; procedure SetSSLData(AIndex: Integer; AValue: TSSLData); procedure SetSSLLastErrorString(AValue: string); protected Function FetchErrorInfo: Boolean; function CheckSSL(SSLResult: Integer): Boolean; function CheckSSL(SSLResult: Pointer): Boolean; function InitContext(NeedCertificate: Boolean): Boolean; virtual; function DoneContext: Boolean; virtual; function InitSslKeys: boolean;virtual; function DoVerifyCert:boolean; public constructor Create; override; Destructor Destroy; override; // Socket methods function Connect : Boolean; override; function Close : Boolean; override; function Accept : Boolean; override; function Shutdown(BiDirectional : Boolean): boolean; override; function Send(Const Buffer; Count: Integer): Integer; override; function Recv(Const Buffer; Count: Integer): Integer; override; function BytesAvailable: Integer; override; Function SSLActive: Boolean; function CreateSelfSignedCertificate(Const AHostName: string): Boolean; virtual; // Result of last CheckSSL call. Function SSLLastError: integer; property SSLLastErrorString: string read FSSLLastErrorString write SetSSLLastErrorString; published property SSLType: TSSLType read FSSLType write FSSLType; {:Password for decrypting of encoded certificate or key.} property Username: string read FUsername write FUsername; property Password: string read FPassword write FPassword; property KeyPassword: string read FKeyPassword write FKeyPassword; property CipherList: string read FCipherList write FCipherList; property Certificate : TSSLData Index 0 Read GetSSLData Write SetSSLData; property TrustedCertificate : TSSLData Index 1 Read GetSSLData Write SetSSLData; property PrivateKey : TSSLData Index 2 Read GetSSLData Write SetSSLData; property PFX: TSSLData Index 3 Read GetSSLData Write SetSSLData; property CertCA: TSSLData Index 4 Read GetSSLData Write SetSSLData; property VerifyPeerCert: Boolean read FVerifyPeerCert Write FVerifyPeerCert; Property SendHostAsSNI : Boolean Read FSendHostAsSNI Write FSendHostAsSNI; // In case a certificate must be generated as server, this is the hostname that will be used. property RemoteHostName : String Read FRemoteHostName Write FRemoteHostName; property OnVerifyCertificate: TVerifyCertificateEvent read FOnVerifyCertificate write FOnVerifyCertificate; end; implementation { TSocketHandler } Resourcestring SErrNoLibraryInit = 'Could not initialize OpenSSL library'; Procedure MaybeInitSSLInterface; begin if not IsSSLloaded then if not InitSSLInterface then Raise EInOutError.Create(SErrNoLibraryInit); end; { TSSLSocketHandler } function TSSLSocketHandler.GetSSLData(AIndex: Integer): TSSLData; begin Result:=FCertData[AIndex]; end; procedure TSSLSocketHandler.SetSSLData(AIndex: Integer; AValue: TSSLData); begin FCertData[AIndex].Assign(AValue); end; procedure TSSLSocketHandler.SetSSLLastErrorString(AValue: string); begin if FSSLLastErrorString=AValue then Exit; FSSLLastErrorString:=AValue; end; function TSSLSocketHandler.DoVerifyCert: boolean; begin Result:=True; If Assigned(OnVerifyCertificate) then OnVerifyCertificate(Self,Result); end; constructor TSSLSocketHandler.Create; Var I : Integer; begin inherited Create; FSendHostAsSNI:=True; MaybeInitSSLInterface; FCipherList:='DEFAULT'; For I:=0 to SSLDataCount do FCertData[i]:=TSSLData.Create; end; Destructor TSSLSocketHandler.Destroy; Var I : Integer; begin FreeAndNil(FSSL); FreeAndNil(FCTX); inherited Destroy; For I:=0 to SSLDataCount do FreeAndNil(FCertData[i]); end; function TSSLSocketHandler.CreateSelfSignedCertificate(Const AHostName: string): Boolean; Const OneDay = 60*60*24; SixtyDays = 60*OneDay; var PK : PEVP_PKEY; X509 : PX509; RSA : PRSA; UTC : PASN1_UTCTIME; SN : PX509_NAME; B : PBIO; begin Result:=False; PK:=Nil; X509:=Nil; try PK:=EvpPkeynew; X509:=X509New; RSA:=RsaGenerateKey(1024,$10001,nil,nil); EvpPkeyAssign(PK,EVP_PKEY_RSA,RSA); X509SetVersion(X509,2); Asn1IntegerSet(X509getSerialNumber(X509),0); UTC:=Asn1UtctimeNew; try X509GmtimeAdj(UTC,-OneDay); X509SetNotBefore(X509,UTC); X509GmtimeAdj(UTC,SixtyDays); X509SetNotAfter(X509,UTC); finally Asn1UtctimeFree(UTC); end; X509SetPubkey(X509,PK); SN:=X509GetSubjectName(X509); X509NameAddEntryByTxt(SN,'C',$1001,'CZ',-1,-1,0); X509NameAddEntryByTxt(SN,'CN',$1001, AHostName,-1,-1,0); x509SetIssuerName(X509,SN); x509Sign(X509,PK,EvpGetDigestByName('SHA1')); B:=BioNew(BioSMem); try i2dX509Bio(B,X509); Certificate.Value:=BioToString(B); finally BioFreeAll(b); end; B:=BioNew(BioSMem); try i2dPrivatekeyBio(B,PK); Privatekey.Value:=BioToString(B); finally BioFreeAll(b); end; finally X509free(X509); EvpPkeyFree(PK); end; end; function TSSLSocketHandler.Connect: Boolean; begin Result:=Inherited Connect; if Result and InitContext(False) then begin Result:=CheckSSL(FSSL.SetFD(FSocket.Handle)); if Result then begin if FSendHostAsSNI and (FSocket is TInetSocket) then FSSL.Ctrl(SSL_CTRL_SET_TLSEXT_HOSTNAME,TLSEXT_NAMETYPE_host_name,PAnsiChar(AnsiString((FSocket as TInetSocket).Host))); Result:=CheckSSL(FSSL.Connect); if Result and VerifyPeerCert then Result:=(FSSL.VerifyResult<>0) or (not DoVerifyCert); if Result then FSSLActive:=True; end; end; end; function TSSLSocketHandler.Close: Boolean; begin Result:=Shutdown(False); end; Function TSSLSocketHandler.FetchErrorInfo : Boolean; var S : AnsiString; begin FSSLLastErrorString:=''; FLastError:=ErrGetError; ErrClearError; Result:=(FLastError>=1); if not Result then begin S:=StringOfChar(#0,256); ErrErrorString(FLastError,S,256); FSSLLastErrorString:=s; end; end; function TSSLSocketHandler.CheckSSL(SSLResult : Integer) : Boolean; begin Result:=SSLResult>=1; if Not Result then begin FLastError:=SSLResult; FetchErrorInfo; end; end; function TSSLSocketHandler.CheckSSL(SSLResult: Pointer): Boolean; begin Result:=(SSLResult<>Nil); if not Result then Result:=FetchErrorInfo; end; function TSSLSocketHandler.DoneContext: Boolean; begin FreeAndNil(FSSL); FreeAndNil(FCTX); ErrRemoveState(0); FSSLActive:=False; Result:=True; end; Function HandleSSLPwd(buf : PAnsiChar; len:Integer; flags:Integer; UD : Pointer):Integer; cdecl; var Pwd: AnsiString; H : TSSLSocketHandler; begin if Not Assigned(UD) then PWD:='' else begin H:=TSSLSocketHandler(UD); Pwd:=H.KeyPassword; end; if (lenNil); if not Result then Exit; if not Certificate.Empty then Result:=CheckSSL(FCTX.UseCertificate(Certificate)); if Result and not PrivateKey.Empty then Result:=CheckSSL(FCTX.UsePrivateKey(PrivateKey)); if Result and (CertCA.FileName<>'') then Result:=CheckSSL(FCTX.LoadVerifyLocations(CertCA.FileName,'')); if Result and not PFX.Empty then Result:=CheckSSL(FCTX.LoadPFX(PFX,Self.KeyPassword)); end; function TSSLSocketHandler.InitContext(NeedCertificate:Boolean): Boolean; Const VO : Array[Boolean] of Integer = (SSL_VERIFY_NONE,SSL_VERIFY_PEER); var s: AnsiString; begin Result:=DoneContext; if Not Result then Exit; try FCTX:=TSSLContext.Create(SSLType); Except CheckSSL(Nil); Result:=False; Exit; end; S:=FCipherList; FCTX.SetCipherList(S); FCTX.SetVerify(VO[FVerifypeerCert],Nil); FCTX.SetDefaultPasswdCb(@HandleSSLPwd); FCTX.SetDefaultPasswdCbUserdata(self); If NeedCertificate and Certificate.Empty and PFX.Empty then if Not CreateSelfSignedCertificate(RemoteHostName) then begin DoneContext; Exit(False); end; if Not InitSSLKeys then begin DoneContext; Exit(False); end; try FSSL:=TSSL.Create(FCTX); Result:=True; Except CheckSSL(Nil); DoneContext; Result:=False; end; end; function TSSLSocketHandler.Accept: Boolean; begin Result:=InitContext(True); if Result then begin Result:=CheckSSL(FSSL.setfd(Socket.Handle)); if Result then Result:=CheckSSL(FSSL.Accept); end; FSSLActive:=Result; end; function TSSLSocketHandler.Shutdown(BiDirectional : Boolean): boolean; var r : integer; begin Result:=assigned(FSsl); if Result then If Not BiDirectional then Result:=CheckSSL(FSSL.Shutdown) else begin r:=FSSL.Shutdown; if r<>0 then Result:=CheckSSL(r) else begin Result:=fpShutdown(FSocket.Handle,1)=0; if Result then Result:=CheckSSL(FSsl.Shutdown); end end; If Result then Result:=DoneContext; end; function TSSLSocketHandler.Send(Const Buffer; Count: Integer): Integer; var e: integer; begin FLastError := 0; FSSLLastErrorString:=''; repeat Result:=FSsl.Write(@Buffer,Count); e:=FSsl.GetError(Result); until Not (e in [SSL_ERROR_WANT_READ,SSL_ERROR_WANT_WRITE]); if (E=SSL_ERROR_ZERO_RETURN) then Result:=0 else if (e<>0) then FLastError:=e; end; function TSSLSocketHandler.Recv(Const Buffer; Count: Integer): Integer; var e: integer; begin FLastError:=0; FSSLLastErrorString:= ''; repeat Result:=FSSL.Read(@Buffer ,Count); e:=FSSL.GetError(Result); until Not (e in [SSL_ERROR_WANT_READ,SSL_ERROR_WANT_WRITE]); if (E=SSL_ERROR_ZERO_RETURN) then Result:=0 else if (e<>0) then FLastError:=e; end; function TSSLSocketHandler.BytesAvailable: Integer; begin Result:= FSSL.Pending; end; Function TSSLSocketHandler.SSLActive: Boolean; begin Result:=FSSLActive; end; Function TSSLSocketHandler.SSLLastError: integer; begin Result:=FLastError; end; end.