mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-16 23:21:57 +02:00
* Initial implementation of GNU TLS support for FPC SSL sockets
git-svn-id: trunk@40858 -
This commit is contained in:
parent
b3ad384547
commit
57e7384a18
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -3840,6 +3840,7 @@ packages/gnutls/examples/srvcacert.pp svneol=native#text/plain
|
||||
packages/gnutls/examples/testgnutls.pp svneol=native#text/plain
|
||||
packages/gnutls/fpmake.pp svneol=native#text/plain
|
||||
packages/gnutls/src/gnutls.pp svneol=native#text/plain
|
||||
packages/gnutls/src/gnutlssockets.pp svneol=native#text/plain
|
||||
packages/googleapi/Makefile svneol=native#text/plain
|
||||
packages/googleapi/Makefile.fpc svneol=native#text/plain
|
||||
packages/googleapi/README.txt svneol=native#text/plain
|
||||
|
@ -25,6 +25,8 @@ begin
|
||||
if Defaults.CPU=jvm then
|
||||
P.OSes := P.OSes - [java,android];
|
||||
T:=P.Targets.AddUnit('gnutls.pp');
|
||||
T:=P.Targets.AddUnit('gnutlssockets.pp');
|
||||
T.Dependencies.AddUnit('gnutls');
|
||||
P.ExamplePath.Add('examples');
|
||||
P.Targets.AddExampleProgram('testgnutls.pp');
|
||||
P.Targets.AddExampleProgram('privkey.pp');
|
||||
|
469
packages/gnutls/src/gnutlssockets.pp
Normal file
469
packages/gnutls/src/gnutlssockets.pp
Normal file
@ -0,0 +1,469 @@
|
||||
{
|
||||
This file is part of the Free Pascal run time library.
|
||||
Copyright (c) 2019 by Michael Van Canneyt, member of the Free Pascal development team
|
||||
|
||||
FPC SSockets SSL support using GnuTLS library.
|
||||
|
||||
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 gnutlssockets;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, sockets, ssockets, sslsockets, cTypes, sslbase, gnutls;
|
||||
|
||||
Type
|
||||
|
||||
{ TGNUTLSSocketHandler }
|
||||
|
||||
TGNUTLSSocketHandler = Class(TSSLSocketHandler)
|
||||
Private
|
||||
FSession : tgnutls_session_t;
|
||||
FCred : tgnutls_certificate_credentials_t;
|
||||
FGNUTLSLastErrorString: string;
|
||||
FGNUTLSLastError : Integer;
|
||||
FCurrentHostName : String;
|
||||
FCurrentCypherlist : String;
|
||||
function LoadTrustedCertificate(aCertData: TSSLData): Boolean;
|
||||
function MaybeAllocateCredentials: Boolean;
|
||||
procedure SetGNUTLSLastErrorString(AValue: string);
|
||||
function SetTrustedCertificateDir(const aFileName: string): Boolean;
|
||||
Protected
|
||||
function LoadCertificate(aData, aKey: TSSLData): Boolean;
|
||||
procedure FreeCredentials;
|
||||
procedure FreeSession;
|
||||
function DoHandShake: Boolean;
|
||||
function GetCipherListString: String; virtual;
|
||||
Function FetchErrorInfo: Boolean;
|
||||
function Check(aResult: cInt) : cInt;
|
||||
function CheckOK(aResult: cInt) : Boolean;
|
||||
function InitSession(AsServer: Boolean): Boolean; virtual;
|
||||
function DoneSession: Boolean; virtual;
|
||||
function InitSslKeys: boolean;virtual;
|
||||
Public
|
||||
Constructor create; override;
|
||||
destructor destroy; override;
|
||||
function CreateCertGenerator: TX509Certificate; override;
|
||||
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;
|
||||
// Result of last CheckSSL call.
|
||||
Function GNUTLSLastError: integer;
|
||||
property GNUTLSLastErrorString: string read FGNUTLSLastErrorString write SetGNUTLSLastErrorString;
|
||||
end;
|
||||
|
||||
{ TGNUTLSX509Certificate }
|
||||
|
||||
TGNUTLSX509Certificate = class(TX509Certificate)
|
||||
|
||||
public
|
||||
function CreateCertificateAndKey: TCertAndKey; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TSocketHandler }
|
||||
|
||||
Procedure MaybeInitGNUTLS;
|
||||
|
||||
begin
|
||||
if not GnuTLSloaded then
|
||||
LoadGnuTLS();
|
||||
end;
|
||||
|
||||
{ TGNUTLSX509Certificate }
|
||||
|
||||
function TGNUTLSX509Certificate.CreateCertificateAndKey: TCertAndKey;
|
||||
begin
|
||||
Result:=Default(TCertAndKey);
|
||||
Raise ENotImplemented.Create('No certificate generation yet');
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.CreateCertGenerator: TX509Certificate;
|
||||
begin
|
||||
Result:=TGNUTLSX509Certificate.Create;
|
||||
end;
|
||||
|
||||
procedure TGNUTLSSocketHandler.SetGNUTLSLastErrorString(AValue: string);
|
||||
begin
|
||||
if FGNUTLSLastErrorString=AValue then Exit;
|
||||
FGNUTLSLastErrorString:=AValue;
|
||||
end;
|
||||
|
||||
|
||||
function TGNUTLSSocketHandler.Connect: Boolean;
|
||||
|
||||
begin
|
||||
Result:=Inherited Connect;
|
||||
// Initsession sets handle
|
||||
Result := Result and InitSession(False);
|
||||
if Not Result then
|
||||
exit;
|
||||
if (Socket is TInetSocket) then
|
||||
begin
|
||||
FCurrentHostName:=(Socket as TInetSocket).Host;
|
||||
if SendHostAsSNI then
|
||||
begin
|
||||
Result:=CheckOK(gnutls_server_name_set(FSession, GNUTLS_NAME_DNS,pchar(FCurrentHostName), length(FCurrentHostName)));
|
||||
if not Result then
|
||||
exit;
|
||||
end;
|
||||
gnutls_session_set_verify_cert(Fsession,pchar(FCurrentHostName),0);
|
||||
end;
|
||||
if Not Result then
|
||||
exit;
|
||||
Result:=DoHandShake;
|
||||
if Result and VerifyPeerCert then
|
||||
Result:=(not DoVerifyCert);
|
||||
if Result then
|
||||
SetSSLActive(True);
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.Close: Boolean;
|
||||
begin
|
||||
Result:=CheckOK(gnutls_bye(FSession,GNUTLS_SHUT_WR));
|
||||
end;
|
||||
|
||||
Function TGNUTLSSocketHandler.FetchErrorInfo : Boolean;
|
||||
|
||||
Var
|
||||
P : Pchar;
|
||||
|
||||
begin
|
||||
FGNUTLSLastErrorString:='';
|
||||
Result:=(FGNUTLSLastError<>0);
|
||||
if Result then
|
||||
begin
|
||||
P:=gnutls_strerror(FGNUTLSLastError);
|
||||
if P<>Nil then
|
||||
FGNUTLSLastErrorString:=StrPas(P)
|
||||
else
|
||||
FGNUTLSLastErrorString:=Format('Unknown error code: %d',[FGNUTLSLastError]);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.CheckOK(aResult: cInt): Boolean;
|
||||
begin
|
||||
Result:=Check(aResult)>=0;
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.Check(aResult: cInt): cInt;
|
||||
begin
|
||||
Result:=aResult;
|
||||
if (Result<>GNUTLS_E_SUCCESS) then
|
||||
begin
|
||||
FGNUTLSLastError:=aResult;
|
||||
FetchErrorInfo;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.GetCipherListString : String;
|
||||
|
||||
begin
|
||||
// Set correct cipher list here
|
||||
if FCurrentCypherlist='' then
|
||||
begin
|
||||
// We need it in a field variable, it seems GNUTLS expects it to last?
|
||||
FCurrentCypherlist:='NORMAL';
|
||||
end;
|
||||
Result:=FCurrentCypherlist;
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.InitSession(AsServer: Boolean): Boolean;
|
||||
|
||||
Const
|
||||
InitFlags : Array[Boolean] of cInt = (GNUTLS_CLIENT,GNUTLS_SERVER);
|
||||
|
||||
Var
|
||||
flags :Cint;
|
||||
errPtr : Pchar;
|
||||
|
||||
begin
|
||||
Flags:=InitFlags[AsServer];
|
||||
Result:=CheckOK(gnutls_init(@FSession,Flags));
|
||||
if not Result then
|
||||
exit;
|
||||
Result:=CheckOK(gnutls_priority_set_direct(FSession, PChar(GetCipherListString), @errptr));
|
||||
if not Result then
|
||||
FGNUTLSLastErrorString:=FGNUTLSLastErrorString+', error at: '+StrPas(errPtr);
|
||||
If AsServer and CertificateData.NeedCertificateData then
|
||||
if Not CreateSelfSignedCertificate then
|
||||
begin
|
||||
DoneSession;
|
||||
Exit(False);
|
||||
end;
|
||||
Result:=InitSslKeys;
|
||||
if not Result then
|
||||
DoneSession;
|
||||
{
|
||||
S:=CertificateData.CipherList;
|
||||
FCTX.SetVerify(VO[VerifypeerCert],Nil);
|
||||
FCTX.SetDefaultPasswdCb(@HandleSSLPwd);
|
||||
FCTX.SetDefaultPasswdCbUserdata(self);
|
||||
}
|
||||
gnutls_transport_set_int2(FSession,Socket.Handle,Socket.Handle);
|
||||
gnutls_handshake_set_timeout(FSession,GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.DoneSession: Boolean;
|
||||
begin
|
||||
FreeSession;
|
||||
FreeCredentials;
|
||||
Result:=True;
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.MaybeAllocateCredentials : Boolean;
|
||||
|
||||
begin
|
||||
Result:=Assigned(FCred);
|
||||
if Result then
|
||||
exit;
|
||||
Result:=CheckOK(gnutls_certificate_allocate_credentials(@FCred));
|
||||
if Result then
|
||||
Result:=CheckOK(gnutls_credentials_set(FSession, GNUTLS_CRD_CERTIFICATE, FCred));
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.LoadCertificate(aData,aKey : TSSLData) : Boolean;
|
||||
var
|
||||
key,cert : tgnutls_datum_t;
|
||||
|
||||
begin
|
||||
result:=MaybeAllocateCredentials;
|
||||
if not Result then exit;
|
||||
if (aData.FileName='') then
|
||||
begin
|
||||
cert.data:=Pointer(aData.Value);
|
||||
cert.size:=Length(aData.Value);
|
||||
key.data:=Pointer(aKey.Value);
|
||||
key.size:=Length(aKey.Value);
|
||||
Result:=CheckOK(gnutls_certificate_set_x509_key_mem(FCred,@cert,@key,GNUTLS_X509_FMT_PEM));
|
||||
if not Result then
|
||||
Result:=CheckOK(gnutls_certificate_set_x509_key_mem(FCred,@cert,@key,GNUTLS_X509_FMT_DER));
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result:=CheckOK(gnutls_certificate_set_x509_key_file (FCred, pChar(aData.FileName),PChar(aKey.FileName),GNUTLS_X509_FMT_PEM));
|
||||
if not Result then
|
||||
Result:=CheckOK(gnutls_certificate_set_x509_key_file (FCred, pChar(aData.FileName),PChar(aKey.FileName),GNUTLS_X509_FMT_DER));
|
||||
end;
|
||||
if Result then
|
||||
Result:=CheckOK(gnutls_credentials_set(FSession, GNUTLS_CRD_CERTIFICATE, FCred));
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.LoadTrustedCertificate(aCertData : TSSLData) : Boolean;
|
||||
|
||||
var
|
||||
ca : tgnutls_datum_t;
|
||||
|
||||
begin
|
||||
MaybeAllocateCredentials;
|
||||
Result:=False;
|
||||
if (aCertData.FileName<>'') then
|
||||
begin
|
||||
Result:=CheckOK(gnutls_certificate_set_x509_trust_file(FCred,PChar(aCertData.FileName),GNUTLS_X509_FMT_PEM));
|
||||
if not Result then
|
||||
Result:=CheckOK(gnutls_certificate_set_x509_trust_file(FCred,PChar(aCertData.FileName),GNUTLS_X509_FMT_DER));
|
||||
end;
|
||||
if (Length(aCertData.Value)>0) then
|
||||
begin
|
||||
ca.data:=Pointer(aCertData.Value);
|
||||
ca.size:=Length(aCertData.Value);
|
||||
Result:=CheckOK(gnutls_certificate_set_x509_trust_mem(FCred,@ca,GNUTLS_X509_FMT_PEM));
|
||||
if not Result then
|
||||
Result:=CheckOK(gnutls_certificate_set_x509_trust_mem(FCred,@ca,GNUTLS_X509_FMT_DER));
|
||||
end;
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.SetTrustedCertificateDir(Const aFileName : string) : Boolean;
|
||||
|
||||
Var
|
||||
P : PChar;
|
||||
|
||||
begin
|
||||
MaybeAllocateCredentials;
|
||||
P:=PAnsiChar(aFileName);
|
||||
if DirectoryExists(aFileName) then
|
||||
begin
|
||||
Result:=CheckOK(gnutls_certificate_set_x509_trust_dir(FCred,P,GNUTLS_X509_FMT_PEM));
|
||||
if not Result then
|
||||
Result:=CheckOK(gnutls_certificate_set_x509_trust_dir(FCred,P,GNUTLS_X509_FMT_DER));
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result:=CheckOK(gnutls_certificate_set_x509_trust_File(FCred,P,GNUTLS_X509_FMT_PEM));
|
||||
if not Result then
|
||||
Result:=CheckOK(gnutls_certificate_set_x509_trust_File(FCred,P,GNUTLS_X509_FMT_DER));
|
||||
end;
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.InitSslKeys: boolean;
|
||||
|
||||
Const
|
||||
DefaultCerts : PChar = '/etc/ssl/certs/ca-certificates.crt';
|
||||
|
||||
begin
|
||||
Result:=(FSession<>Nil);
|
||||
if not Result then
|
||||
Exit;
|
||||
if not (CertificateData.NeedCertificateData) then
|
||||
Result:=LoadCertificate(CertificateData.Certificate,CertificateData.PrivateKey);
|
||||
if Result and Not CertificateData.TrustedCertificate.Empty then
|
||||
Result:=LoadTrustedCertificate(CertificateData.TrustedCertificate);
|
||||
if Result and (CertificateData.CertCA.FileName<>'') then
|
||||
Result:=Result and SetTrustedCertificateDir(CertificateData.CertCA.FileName);
|
||||
// If nothing was set, set defaults.
|
||||
if not Assigned(FCred) then
|
||||
begin
|
||||
Result:=MaybeAllocateCredentials;
|
||||
if Result then
|
||||
Result:=CheckOK(gnutls_certificate_set_x509_system_trust(FCred));
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TGNUTLSSocketHandler.create;
|
||||
begin
|
||||
inherited create;
|
||||
MaybeInitGNUTLS;
|
||||
end;
|
||||
|
||||
destructor TGNUTLSSocketHandler.destroy;
|
||||
begin
|
||||
DoneSession;
|
||||
FreeSession;
|
||||
FreeCredentials;
|
||||
inherited destroy;
|
||||
end;
|
||||
|
||||
Procedure TGNUTLSSocketHandler.FreeSession;
|
||||
|
||||
begin
|
||||
If (FSession<>Nil) then
|
||||
begin
|
||||
gnutls_deinit(Fsession);
|
||||
Fsession:=Nil;
|
||||
end;
|
||||
end;
|
||||
|
||||
Procedure TGNUTLSSocketHandler.FreeCredentials;
|
||||
|
||||
begin
|
||||
If (FCred<>Nil) then
|
||||
begin
|
||||
gnutls_certificate_free_credentials(FCred);
|
||||
FCred:=Nil;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.DoHandShake : Boolean;
|
||||
|
||||
Var
|
||||
Ret : cInt;
|
||||
|
||||
begin
|
||||
Repeat
|
||||
ret:=Check(gnutls_handshake(FSession));
|
||||
until (ret>=0) or (gnutls_error_is_fatal(ret) <> 0);
|
||||
Result:=Ret>=0;
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.Accept: Boolean;
|
||||
|
||||
begin
|
||||
Result:=InitSession(True);
|
||||
if Result then
|
||||
Result:=DoHandShake;
|
||||
SetSSLActive(Result);
|
||||
end;
|
||||
|
||||
|
||||
function TGNUTLSSocketHandler.Shutdown(BiDirectional : Boolean): boolean;
|
||||
|
||||
begin
|
||||
Result:=assigned(FSession);
|
||||
if Result then
|
||||
If Not BiDirectional then
|
||||
gnutls_bye(FSession, GNUTLS_SHUT_WR)
|
||||
else
|
||||
begin
|
||||
Result:=CheckOK(gnutls_bye(FSession, GNUTLS_SHUT_RDWR));
|
||||
if Result then
|
||||
Result:=fpShutdown(Socket.Handle,1)=0;
|
||||
end;
|
||||
If Result then
|
||||
Result:=DoneSession;
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.Send(Const Buffer; Count: Integer): Integer;
|
||||
|
||||
Var
|
||||
Ret : Integer;
|
||||
P : PByte;
|
||||
|
||||
begin
|
||||
Result:=0;
|
||||
P:=PByte(@Buffer);
|
||||
Repeat
|
||||
Ret:=Check(gnutls_record_send(Fsession,P,Count));
|
||||
if Ret>0 then
|
||||
begin
|
||||
Result:=Result+Ret;
|
||||
Inc(P,Ret);
|
||||
end;
|
||||
Until (Result=Count) or ((Ret<0) and (gnutls_error_is_fatal(ret)<>0));
|
||||
if Result=Count then
|
||||
exit;
|
||||
if Ret<0 then
|
||||
Result:=-1;
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.Recv(Const Buffer; Count: Integer): Integer;
|
||||
|
||||
Var
|
||||
Ret : Integer;
|
||||
P : PByte;
|
||||
|
||||
begin
|
||||
Result:=0;
|
||||
P:=PByte(@Buffer);
|
||||
Repeat
|
||||
Ret:=Check(gnutls_record_recv(FSession,P,Count));
|
||||
if Ret>0 then
|
||||
begin
|
||||
Result:=Result+Ret;
|
||||
Inc(P,Ret);
|
||||
Dec(Count,Ret)
|
||||
end;
|
||||
Until (Count=0) or ((Ret<0) and (gnutls_error_is_fatal(ret)<>0));
|
||||
if Count=0 then
|
||||
exit;
|
||||
if Ret<0 then
|
||||
Result:=-1;
|
||||
end;
|
||||
|
||||
function TGNUTLSSocketHandler.BytesAvailable: Integer;
|
||||
begin
|
||||
Result:=gnutls_record_check_pending(FSession);
|
||||
end;
|
||||
|
||||
Function TGNUTLSSocketHandler.GNUTLSLastError: integer;
|
||||
begin
|
||||
Result:=FGNUTLSLastError;
|
||||
end;
|
||||
|
||||
initialization
|
||||
TSSLSocketHandler.SetDefaultHandlerClass(TGNUTLSSocketHandler);
|
||||
end.
|
||||
|
Loading…
Reference in New Issue
Block a user