unit IdSSLDotNET; interface {$i IdCompilerDefines.inc} {*******************************************************} { } { Indy SSL Support for Microsoft.NET 2.0 } { } { Copyright (C) 2007 Indy Pit Crew } { Original author J. Peter Mugaas } { 2007-Aug-22 } { } {*******************************************************} uses Classes, IdException, IdGlobal, IdIOHandler, IdSocketHandle, IdSSL, IdThread, IdYarn, System.Collections, System.IO, System.Net.Sockets, System.Net.Security, System.Security.Authentication, System.Security.Cryptography.X509Certificates; const DEF_clientCertificateRequired = False; DEF_checkCertificateRevocation = True; type TOnValidatePeerCertificate = procedure (ASender : TObject; ACertificate : X509Certificate; AChain : X509Chain; AsslPolicyErrors : SslPolicyErrors; var VValid : Boolean) of object; TOnLocalCertificateSelectionCallback = procedure (ASender : TObject; AtargetHost : String; AlocalCertificates : X509CertificateCollection; AremoteCertificate : X509Certificate; AacceptableIssuers : array of String; VCert : X509Certificate) of object; TIdSSLIOHandlerSocketNET = class(TIdSSLIOHandlerSocketBase) protected FenabledSslProtocols: System.Security.Authentication.SslProtocols; FOnValidatePeerCertificate : TOnValidatePeerCertificate; FOnLocalCertificateSelection : TOnLocalCertificateSelectionCallback; FSSL : SslStream; FServerCertificate : X509Certificate; FClientCertificates : X509CertificateCollection; FOnSSLHandshakeDone : TNotifyEvent; FclientCertificateRequired : Boolean; FcheckCertificateRevocation : Boolean; procedure OpenEncodedConnection; virtual; //Ssl certificate validation callback function ValidatePeerCertificate( sender : System.&Object; certificate : X509Certificate; chain : X509Chain; sslPolicyErrors : SslPolicyErrors) : Boolean; function LocalCertificateSelectionCallback ( sender : System.&Object; targetHost : String; localCertificates : X509CertificateCollection; remoteCertificate : X509Certificate; acceptableIssuers : array of String) : X509Certificate; function RecvEnc(var VBuffer: TIdBytes): Integer; override; function SendEnc(const ABuffer: TIdBytes; const AOffset, ALength: Integer): Integer; override; procedure SetPassThrough(const Value: Boolean); override; procedure InitComponent; override; procedure ConnectClient; override; // function GetCipherAlgorithm: CipherAlgorithmType; function GetCipherStrength: Integer; function GetHashAlgorithm: HashAlgorithmType; function GetHashStrength: Integer; function GetIsEncrypted: Boolean; function GetIsSigned: Boolean; function GetKeyExchangeAlgorithm: ExchangeAlgorithmType; function GetKeyExchangeStrength: Integer; function GetRemoteCertificate: X509Certificate; function GetSslProtocol: SslProtocols; public procedure Close; override; procedure StartSSL; override; function Clone : TIdSSLIOHandlerSocketBase; override; property CipherAlgorithm : CipherAlgorithmType read GetCipherAlgorithm; property CipherStrength : Integer read GetCipherStrength; property HashAlgorithm : HashAlgorithmType read GetHashAlgorithm; property HashStrength : Integer read GetHashStrength; property IsEncrypted : Boolean read GetIsEncrypted; property IsSigned : Boolean read GetIsSigned; property KeyExchangeAlgorithm : ExchangeAlgorithmType read GetKeyExchangeAlgorithm; property KeyExchangeStrength : Integer read GetKeyExchangeStrength; property RemoteCertificate : X509Certificate read GetRemoteCertificate; property SslProtocol : SslProtocols read GetSslProtocol; published property enabledSslProtocols : System.Security.Authentication.SslProtocols read FenabledSslProtocols write FenabledSslProtocols; property ServerCertificate : X509Certificate read FServerCertificate write FServerCertificate; property ClientCertificates : X509CertificateCollection read FClientCertificates write FClientCertificates; property clientCertificateRequired : Boolean read FclientCertificateRequired write FclientCertificateRequired; property checkCertificateRevocation : Boolean read FcheckCertificateRevocation write FcheckCertificateRevocation; property OnSSLHandshakeDone : TNotifyEvent read FOnSSLHandshakeDone write FOnSSLHandshakeDone; property OnLocalCertificateSelection : TOnLocalCertificateSelectionCallback read FOnLocalCertificateSelection write FOnLocalCertificateSelection; property OnValidatePeerCertificate : TOnValidatePeerCertificate read FOnValidatePeerCertificate write FOnValidatePeerCertificate; end; TIdServerIOHandlerSSLNET = class(TIdServerIOHandlerSSLBase) protected FOnValidatePeerCertificate : TOnValidatePeerCertificate; FOnLocalCertificateSelection : TOnLocalCertificateSelectionCallback; FOnSSLHandshakeDone : TNotifyEvent; FenabledSslProtocols : System.Security.Authentication.SslProtocols; FServerCertificate : X509Certificate; FclientCertificateRequired : Boolean; FcheckCertificateRevocation : Boolean; procedure InitComponent; override; procedure SetIOHandlerValues(AIO : TIdSSLIOHandlerSocketNET); published public destructor Destroy; override; procedure Init; override; procedure Shutdown; override; function MakeClientIOHandler : TIdSSLIOHandlerSocketBase; override; // function MakeFTPSvrPort : TIdSSLIOHandlerSocketBase; override; function MakeFTPSvrPasv : TIdSSLIOHandlerSocketBase; override; function Accept(ASocket: TIdSocketHandle; AListenerThread: TIdThread; AYarn: TIdYarn): TIdIOHandler; override; published property enabledSslProtocols : System.Security.Authentication.SslProtocols read FenabledSslProtocols write FenabledSslProtocols; property ServerCertificate : X509Certificate read FServerCertificate write FServerCertificate; property clientCertificateRequired : Boolean read FclientCertificateRequired write FclientCertificateRequired; property checkCertificateRevocation : Boolean read FcheckCertificateRevocation write FcheckCertificateRevocation; property OnSSLHandshakeDone : TNotifyEvent read FOnSSLHandshakeDone write FOnSSLHandshakeDone; property OnLocalCertificateSelection : TOnLocalCertificateSelectionCallback read FOnLocalCertificateSelection write FOnLocalCertificateSelection; property OnValidatePeerCertificate : TOnValidatePeerCertificate read FOnValidatePeerCertificate write FOnValidatePeerCertificate; end; EIdSSLNetException = class(EIdException); EIdSSLCertRequiredForSvr = class(EIdSSLNetException); EIdSSLNotAuthenticated = class(EIdSSLNetException); implementation uses IdResourceStringsSSLDotNet, IdStack, SysUtils; { TIdSSLIOHandlerSocketNET } function TIdSSLIOHandlerSocketNET.Clone: TIdSSLIOHandlerSocketBase; begin Result := TIdSSLIOHandlerSocketNET.Create(nil); TIdSSLIOHandlerSocketNET(Result).FenabledSslProtocols := FenabledSslProtocols; TIdSSLIOHandlerSocketNET(Result).FOnValidatePeerCertificate := FOnValidatePeerCertificate; TIdSSLIOHandlerSocketNET(Result).FOnLocalCertificateSelection := FOnLocalCertificateSelection; TIdSSLIOHandlerSocketNET(Result).FServerCertificate := FServerCertificate; TIdSSLIOHandlerSocketNET(Result).FClientCertificates := FClientCertificates; TIdSSLIOHandlerSocketNET(Result).FOnSSLHandshakeDone := FOnSSLHandshakeDone; end; procedure TIdSSLIOHandlerSocketNET.Close; begin if Assigned(FSSL) then begin FSSL.Close; FreeAndNil(FSSL); end; inherited; end; procedure TIdSSLIOHandlerSocketNET.ConnectClient; var LPassThrough: Boolean; begin // RLebeau 1/11/07: In case a proxy is being used, pass through // any data from the base class unencrypted when setting up that // connection. We should do this anyway since SSL hasn't been // initialized yet! LPassThrough := fPassThrough; fPassThrough := True; try inherited ConnectClient; finally fPassThrough := LPassThrough; end; StartSSL; end; function TIdSSLIOHandlerSocketNET.GetCipherAlgorithm: CipherAlgorithmType; begin if Assigned(FSSL) then begin Result := FSSL.CipherAlgorithm; end else begin Result := System.Security.Authentication.CipherAlgorithmType.None; end; end; function TIdSSLIOHandlerSocketNET.GetCipherStrength: Integer; begin if Assigned(FSSL) then begin Result := FSSL.CipherStrength; end else begin Result := 0; end; end; function TIdSSLIOHandlerSocketNET.GetHashAlgorithm: HashAlgorithmType; begin if Assigned(FSSL) then begin Result := FSSL.HashAlgorithm; end else begin Result := HashAlgorithmType.None; end; end; function TIdSSLIOHandlerSocketNET.GetHashStrength: Integer; begin if Assigned(FSSL) then begin Result := FSSL.HashStrength; end else begin Result := 0; end; end; function TIdSSLIOHandlerSocketNET.GetIsEncrypted: Boolean; begin if Assigned(FSSL) then begin Result := FSSL.IsEncrypted; end else begin Result := False; end; end; function TIdSSLIOHandlerSocketNET.GetIsSigned: Boolean; begin if Assigned(FSSL) then begin Result := FSSL.IsSigned; end else begin Result := False; end; end; function TIdSSLIOHandlerSocketNET.GetKeyExchangeAlgorithm: ExchangeAlgorithmType; begin if Assigned(FSSL) then begin Result := FSSL.KeyExchangeAlgorithm; end else begin Result := ExchangeAlgorithmType.None; end; end; function TIdSSLIOHandlerSocketNET.GetKeyExchangeStrength: Integer; begin if Assigned(FSSL) then begin Result := FSSL.KeyExchangeStrength; end else begin Result := 0; end; end; function TIdSSLIOHandlerSocketNET.GetRemoteCertificate: X509Certificate; begin if Assigned(FSSL) then begin Result := FSSL.RemoteCertificate; end else begin Result := nil; end; end; function TIdSSLIOHandlerSocketNET.GetSslProtocol: SslProtocols; begin if Assigned(FSSL) then begin Result := FSSL.SslProtocol; end else begin Result := SslProtocols.None; end; end; procedure TIdSSLIOHandlerSocketNET.InitComponent; begin inherited; FenabledSslProtocols := System.Security.Authentication.SslProtocols.Default; FclientCertificateRequired := DEF_clientCertificateRequired; FcheckCertificateRevocation := DEF_checkCertificateRevocation; end; function TIdSSLIOHandlerSocketNET.LocalCertificateSelectionCallback( sender: TObject; targetHost: String; localCertificates: X509CertificateCollection; remoteCertificate: X509Certificate; acceptableIssuers: array of String): X509Certificate; var i : Integer; LIssuer : String; begin Result := nil; if Assigned(FOnLocalCertificateSelection) then begin FOnLocalCertificateSelection(Self,targetHost,localCertificates,remoteCertificate,Acceptableissuers,Result); end else begin if Assigned(acceptableIssuers) and (Length(acceptableIssuers)>0) and Assigned(localCertificates) and (localCertificates.Count > 0) then begin // Use the first certificate that is from an acceptable issuer. for I := 0 to LocalCertificates.Count -1 do begin LIssuer := LocalCertificates[i].Issuer; if (System.Array.IndexOf(acceptableIssuers, Lissuer)>-1) then begin Result := LocalCertificates[i]; Exit; end; end; end; end; if (localCertificates <> nil) and (localCertificates.Count > 0) then begin Result := localCertificates[0]; end; end; procedure TIdSSLIOHandlerSocketNET.OpenEncodedConnection; begin FSSL := System.Net.Security.SslStream.Create( System.Net.Sockets.NetworkStream.Create(FBinding.Handle,False),False, ValidatePeerCertificate,LocalCertificateSelectionCallback); if IsPeer then begin if Assigned(FServerCertificate) then begin FSSL.AuthenticateAsServer(FServerCertificate,FclientCertificateRequired,FenabledSslProtocols,FcheckCertificateRevocation); end else begin raise EIdSSLCertRequiredForSvr.Create(RSSSLNETCertificateRequired); end; end else begin if Assigned(FClientCertificates) then begin FSSL.AuthenticateAsClient(FHost,FClientCertificates,FenabledSslProtocols,True); if not FSSL.IsMutuallyAuthenticated then begin raise EIdSSLNotAuthenticated.Create(RSSSLNETNotAuthenticated); end; end else begin FSSL.AuthenticateAsClient(FHost,nil,FenabledSslProtocols,True); if not FSSL.IsAuthenticated then begin raise EIdSSLNotAuthenticated.Create(RSSSLNETNotAuthenticated); end; end; end; if Assigned(FOnSSLHandshakeDone) then begin FOnSSLHandshakeDone(Self); end; end; function TIdSSLIOHandlerSocketNET.RecvEnc(var VBuffer: TIdBytes): Integer; begin Result := FSSL.Read(VBuffer,0,Length(VBuffer)); end; function TIdSSLIOHandlerSocketNET.SendEnc(const ABuffer: TIdBytes; const AOffset, ALength: Integer): Integer; begin FSSL.Write(ABuffer,AOffset,ALength); Result := ALength; end; procedure TIdSSLIOHandlerSocketNET.SetPassThrough(const Value: Boolean); begin if fPassThrough <> Value then begin if not Value then begin if BindingAllocated then begin OpenEncodedConnection; end; end; fPassThrough := Value; end; end; procedure TIdSSLIOHandlerSocketNET.StartSSL; begin if not PassThrough then begin OpenEncodedConnection; end; end; function TIdSSLIOHandlerSocketNET.ValidatePeerCertificate(sender: TObject; certificate: X509Certificate; chain: X509Chain; sslPolicyErrors: SslPolicyErrors): Boolean; begin if Assigned(FOnValidatePeerCertificate) then begin FOnValidatePeerCertificate(sender,certificate,chain,sslPolicyErrors, Result); end else begin { This is a workaround for a quirk. If using this as a server, the validation routine may be called even though there may not be a client certificate and FclientCertificateRequired was set to false. It might be by design though. } case sslPolicyErrors of System.Net.Security.SslPolicyErrors.None : Result := True; System.Net.Security.SslPolicyErrors.RemoteCertificateNotAvailable : begin if IsPeer and (not FclientCertificateRequired) then begin Result := True; end else begin Result := False; end; end; else Result := False; end; end; end; { TIdServerIOHandlerSSLNET } function TIdServerIOHandlerSSLNET.Accept(ASocket: TIdSocketHandle; AListenerThread: TIdThread; AYarn: TIdYarn): TIdIOHandler; var LIO : TIdSSLIOHandlerSocketNET; begin LIO := TIdSSLIOHandlerSocketNET.Create(nil); LIO.PassThrough := True; LIO.IsPeer := True; LIO.Open; if LIO.Binding.Accept(ASocket.Handle) then begin SetIOHandlerValues(LIO); Result := LIO; end else begin Result := nil; FreeAndNil(LIO); end; end; destructor TIdServerIOHandlerSSLNET.Destroy; begin inherited; end; procedure TIdServerIOHandlerSSLNET.Init; begin inherited; end; procedure TIdServerIOHandlerSSLNET.InitComponent; begin inherited InitComponent; FenabledSslProtocols := System.Security.Authentication.SslProtocols.Default; FclientCertificateRequired := DEF_clientCertificateRequired; FcheckCertificateRevocation := DEF_checkCertificateRevocation; end; function TIdServerIOHandlerSSLNET.MakeClientIOHandler: TIdSSLIOHandlerSocketBase; var LIO : TIdSSLIOHandlerSocketNET; begin LIO := TIdSSLIOHandlerSocketNET.Create(nil); LIO.PassThrough := True; LIO.IsPeer := False; SetIOHandlerValues(LIO); Result := LIO; end; function TIdServerIOHandlerSSLNET.MakeFTPSvrPasv: TIdSSLIOHandlerSocketBase; var LIO : TIdSSLIOHandlerSocketNET; begin LIO := TIdSSLIOHandlerSocketNET.Create(nil); LIO.PassThrough := True; LIO.IsPeer := True; SetIOHandlerValues(LIO); Result := LIO; end; function TIdServerIOHandlerSSLNET.MakeFTPSvrPort: TIdSSLIOHandlerSocketBase; var LIO : TIdSSLIOHandlerSocketNET; begin LIO := TIdSSLIOHandlerSocketNET.Create(nil); LIO.PassThrough := True; LIO.IsPeer := True; SetIOHandlerValues(LIO); Result := LIO; end; procedure TIdServerIOHandlerSSLNET.SetIOHandlerValues( AIO: TIdSSLIOHandlerSocketNET); begin AIO.FServerCertificate := FServerCertificate; AIO.FclientCertificateRequired := FclientCertificateRequired; // AIO.FClientCertificates := FClientCertificates; AIO.FcheckCertificateRevocation := FcheckCertificateRevocation; AIO.FOnSSLHandshakeDone := FOnSSLHandshakeDone; AIO.FenabledSslProtocols := FenabledSslProtocols; AIO.FOnLocalCertificateSelection := FOnLocalCertificateSelection; AIO.FOnValidatePeerCertificate := FOnValidatePeerCertificate; end; procedure TIdServerIOHandlerSSLNET.Shutdown; begin inherited; end; initialization RegisterSSL('Indy SSL Support for Microsoft.NET 2.0','Indy Pit Crew', {do not localize} 'Copyright © 1993 - 2007'#10#13 + {do not localize} 'Chad Z. Hower (Kudzu) and the Indy Pit Crew. All rights reserved.', {do not localize} 'Open SSL Support DLL Delphi and C++Builder interface', {do not localize} 'http://www.indyproject.org/'#10#13 + {do not localize} 'Original Author - J. Peter Mugaas', {do not localize} TIdSSLIOHandlerSocketNET, TIdServerIOHandlerSSLNET); TIdSSLIOHandlerSocketNET.RegisterIOHandler; end.