Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
fpc-src / usr / share / fpcsrc / 3.2.0 / packages / gnutls / src / gnutlssockets.pp
Size: Mime:
unit gnutlssockets;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, sockets, ssockets, sslsockets, dateUtils,
  cTypes, sslbase, gnutls;

Const
  DefCertSize = 8192;

Type
  EGnuTLS = Class(Exception);

  { 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)
  private
    FMyFormat : tgnutls_x509_crt_fmt_t;
    procedure Check(Aret: cint);
    procedure Check(Aexp: Boolean; Aret: cint);
    function GenCACert(const Aca_priv_key: TBytes; const Acommon_name, Aserial: AnsiString; Adays: Word): TBytes;
    function GenPrivKey: TBytes;
    function GenSrvCert(const Aca_priv_key, Aca_pem, Asrv_priv_key: TBytes; const Acommon_name, Aorganization, Aserial: AnsiString;
      Adays: Word): TBytes;
  public
    constructor create;
    function CreateCertificateAndKey: TCertAndKey; override;
  end;

implementation

{ TSocketHandler }

Procedure MaybeInitGNUTLS;

begin
  if not GnuTLSloaded then
     LoadGnuTLS();
end;

{ TGNUTLSX509Certificate }

procedure TGNUTLSX509Certificate.Check(Aret: cint); inline;

begin
  if Aret <> GNUTLS_E_SUCCESS then
    raise EGnuTLS.Create(gnutls_strerror(Aret));
end;

procedure TGNUTLSX509Certificate.Check(Aexp: Boolean; Aret: cint); inline;
begin
  if Aexp then
    raise EGnuTLS.Create(gnutls_strerror(Aret));
end;

function TGNUTLSX509Certificate.GenPrivKey : TBytes;

var
  akey: Tgnutls_x509_privkey_t;
  aSize: cuint;

begin
  Result:=Default(TBytes);
  try
    Check(gnutls_x509_privkey_init(@akey));
    aSize := gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_HIGH);
    SetLength(Result,asize);
    Check(gnutls_x509_privkey_generate(akey, GNUTLS_PK_RSA, aSize, 0));
    Check(gnutls_x509_privkey_export(akey,FMyFormat,Pointer(Result), @aSize));
    SetLength(Result,asize);
  except
    gnutls_x509_privkey_deinit(akey);
    raise;
  end;
end;


Function TGNUTLSX509Certificate.GenCACert(const Aca_priv_key: TBytes; const Acommon_name, Aserial: AnsiString; Adays: Word) : TBytes;

var
  Vkey: Tgnutls_x509_privkey_t;
  Vcrt: Tgnutls_x509_crt_t = nil;
  Vdata: Tgnutls_datum_t;
  Vkeyid: TBytes;
  Vkeyidsize: csize_t;
  Vactivation: ttime_t;
  Vca_pem_size: csize_t;
  Vret: cint;

begin
  Vkeyid:=Default(TBytes);
  Result:=Default(TBytes);
  try
    Check(gnutls_x509_privkey_init(@Vkey));
    Vdata.data := Pointer(Aca_priv_key);
    Vdata.size := Length(Aca_priv_key);
    Check(gnutls_x509_privkey_import(Vkey, @Vdata, FMyFormat));
    Check(gnutls_x509_crt_init(@Vcrt));
    Check(gnutls_x509_crt_set_key(Vcrt, Vkey));
    Check(gnutls_x509_crt_set_dn_by_oid(Vcrt, GNUTLS_OID_X520_COMMON_NAME,0, @Acommon_name[1], Length(Acommon_name)));
    Check(gnutls_x509_crt_set_version(Vcrt, 3));
    Check(gnutls_x509_crt_set_serial(Vcrt, @Aserial[1], Length(Aserial)));
    Vactivation := DateTimeToUnix(Now,False);
    Check(gnutls_x509_crt_set_activation_time(Vcrt, Vactivation));
    Check(gnutls_x509_crt_set_expiration_time(Vcrt, Vactivation + (Adays * 86400)));
    Check(gnutls_x509_crt_set_ca_status(Vcrt, Ord(True)));
    Check(gnutls_x509_crt_set_key_usage(Vcrt, GNUTLS_KEY_KEY_CERT_SIGN));
    Vkeyidsize := 0;
    Vret := gnutls_x509_crt_get_key_id(Vcrt, GNUTLS_KEYID_USE_SHA1, nil, @Vkeyidsize);
    Check((Vret <> GNUTLS_E_SHORT_MEMORY_BUFFER) or (Vkeyidsize < 1), Vret);
    SetLength(Vkeyid, Pred(Vkeyidsize));
    Check(gnutls_x509_crt_get_key_id(Vcrt, GNUTLS_KEYID_USE_SHA1, Pointer(Vkeyid), @Vkeyidsize));
    Check(gnutls_x509_crt_set_subject_key_id(Vcrt, Pointer(Vkeyid), Vkeyidsize));
    Check(gnutls_x509_crt_sign2(Vcrt, Vcrt, Vkey, GNUTLS_DIG_SHA256, 0));
    SetLength(Result, DefCertSize);
    Check(gnutls_x509_crt_export(Vcrt, FMyFormat, Pointer(Result), @Vca_pem_size));
    SetLength(Result, Pred(Vca_pem_size));
  except
    gnutls_x509_privkey_deinit(Vkey);
    gnutls_x509_crt_deinit(Vcrt);
    raise;
  end;
end;

Function TGNUTLSX509Certificate.GenSrvCert(const Aca_priv_key, Aca_pem, Asrv_priv_key: TBytes;  const Acommon_name, Aorganization,  Aserial: AnsiString; Adays: Word) : TBytes;

var
  Vsrv_key: Tgnutls_x509_privkey_t = nil;
  Vca_key: Tgnutls_x509_privkey_t = nil;
  Vca_crt: Tgnutls_x509_crt_t = nil;
  Vsrv_crt: Tgnutls_x509_crt_t = nil;
  Vdata: Tgnutls_datum_t;
  Vkeyid: TBytes;
  Vkeyidsize: csize_t;
  Vactivation: ttime_t;
  Vsrv_pem_size: csize_t;
  Vret: cint;

begin
  Vkeyid:=Default(TBytes);
  Result:=Default(TBytes);
  try
    Check(gnutls_x509_privkey_init(@Vca_key));
    Vdata.data := Pointer(Aca_priv_key);
    Vdata.size := Length(Aca_priv_key);
    Check(gnutls_x509_privkey_import(Vca_key, @Vdata, FMyFormat));
    Check(gnutls_x509_privkey_init(@Vsrv_key));
    Vdata.data := Pointer(Asrv_priv_key);
    Vdata.size := Length(Asrv_priv_key);
    Check(gnutls_x509_privkey_import(Vsrv_key, @Vdata, FMyFormat));
    Check(gnutls_x509_crt_init(@Vca_crt));
    Vdata.data := Pointer(Aca_pem);
    Vdata.size := Length(Aca_pem);
    Check(gnutls_x509_crt_import(Vca_crt, @Vdata, FMyFormat));
    Check(gnutls_x509_crt_init(@Vsrv_crt));
    Check(gnutls_x509_crt_set_key(Vsrv_crt, Vsrv_key));
    Check(gnutls_x509_crt_set_dn_by_oid(Vsrv_crt, GNUTLS_OID_X520_COMMON_NAME, 0, @Acommon_name[1], Length(Acommon_name)));
    if (AOrganization<>'') then
      Check(gnutls_x509_crt_set_dn_by_oid(Vsrv_crt, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, @Aorganization[1], Length(Aorganization)));
    Check(gnutls_x509_crt_set_version(Vsrv_crt, 3));
    Check(gnutls_x509_crt_set_serial(Vsrv_crt, @Aserial[1],Length(Aserial)));
    Vactivation := DateTimeToUnix(Now,False);
    Check(gnutls_x509_crt_set_activation_time(Vsrv_crt, Vactivation));
    Check(gnutls_x509_crt_set_expiration_time(Vsrv_crt, Vactivation + (Adays * 86400)));
    Check(gnutls_x509_crt_set_ca_status(Vsrv_crt, Ord(False)));
    Check(gnutls_x509_crt_set_key_purpose_oid(Vsrv_crt,  @GNUTLS_KP_TLS_WWW_SERVER[1], Ord(False)));
    Vkeyidsize := 0;
    Vret := gnutls_x509_crt_get_subject_key_id(Vca_crt, nil, @Vkeyidsize, nil);
    Check((Vret <> GNUTLS_E_SHORT_MEMORY_BUFFER) or  (Vkeyidsize < 1), Vret);
    SetLength(Vkeyid, Pred(Vkeyidsize));
    Check(gnutls_x509_crt_get_subject_key_id(Vca_crt, Pointer(Vkeyid), @Vkeyidsize, nil));
    Check(gnutls_x509_crt_set_subject_key_id(Vsrv_crt, Pointer(Vkeyid), Vkeyidsize));
    Vkeyidsize := 0;
    gnutls_x509_crt_get_key_id(Vsrv_crt, GNUTLS_KEYID_USE_SHA1, nil, @Vkeyidsize);
    Check((Vret <> GNUTLS_E_SHORT_MEMORY_BUFFER) or (Vkeyidsize < 1), Vret);
    SetLength(Vkeyid, Pred(Vkeyidsize));
    Check(gnutls_x509_crt_get_key_id(Vsrv_crt, GNUTLS_KEYID_USE_SHA1, Pointer(Vkeyid), @Vkeyidsize));
    Check(gnutls_x509_crt_set_authority_key_id(Vsrv_crt,  Pointer(Vkeyid), Vkeyidsize));
    Check(gnutls_x509_crt_sign2(Vsrv_crt, Vca_crt, Vca_key,  GNUTLS_DIG_SHA256, 0));
    Vsrv_pem_size := DefCertSize;
    SetLength(Result, Pred(Vsrv_pem_size));
    Check(gnutls_x509_crt_export(Vsrv_crt, FMyFormat,Pointer(Result), @Vsrv_pem_size));
    SetLength(Result, Vsrv_pem_size);
  except
    gnutls_x509_privkey_deinit(Vsrv_key);
    gnutls_x509_privkey_deinit(Vca_key);
    gnutls_x509_crt_deinit(Vca_crt);
    gnutls_x509_crt_deinit(Vsrv_crt);
    raise;
  end;
end;

constructor TGNUTLSX509Certificate.create;
begin
  FMyFormat:=GNUTLS_X509_FMT_PEM;
end;

function TGNUTLSX509Certificate.CreateCertificateAndKey: TCertAndKey;

Var
  PK,cacert : TBytes;

begin
  Result:=Default(TCertAndKey);
  PK:=GenPrivKey;
  CaCErt:=GenCACert(PK,Self.HostName,IntToStr(Serial),30);
  Result.PrivateKey:=PK;
  Result.Certificate:=GenSrvCert(PK,CaCert,PK,Self.HostName,'',IntToStr(Serial),30);
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;

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
  P : PByte;

begin
  P:=PByte(@Buffer);
  Result:=Check(gnutls_record_send(Fsession,P,Count));
  if Result<0 then
    Result:=-1;
end;

function TGNUTLSSocketHandler.Recv(Const Buffer; Count: Integer): Integer;

Var
  P : PByte;

begin
  P:=PByte(@Buffer);
  Result:=Check(gnutls_record_recv(FSession,P,Count));
  if Result<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.