@@ -55,8 +55,8 @@ public sealed partial class NpgsqlConnector
5555 /// </summary>
5656 public NpgsqlConnectionStringBuilder Settings { get ; }
5757
58- Action < X509CertificateCollection > ? ClientCertificatesCallback { get ; }
59- RemoteCertificateValidationCallback ? UserCertificateValidationCallback { get ; }
58+ Action < SslClientAuthenticationOptions > ? SslClientAuthenticationOptionsCallback { get ; }
59+
6060#pragma warning disable CS0618 // ProvidePasswordCallback is obsolete
6161 ProvidePasswordCallback ? ProvidePasswordCallback { get ; }
6262#pragma warning restore CS0618
@@ -282,7 +282,11 @@ internal bool PostgresCancellationPerformed
282282 internal bool AttemptPostgresCancellation { get ; private set ; }
283283 static readonly TimeSpan _cancelImmediatelyTimeout = TimeSpan . FromMilliseconds ( - 1 ) ;
284284
285+ #pragma warning disable CA1859
286+ // We're casting to IDisposable to not explicitly reference X509Certificate2 for NativeAOT
287+ // TODO: probably pointless now, needs to be rechecked
285288 IDisposable ? _certificate ;
289+ #pragma warning restore CA1859
286290
287291 internal NpgsqlLoggingConfiguration LoggingConfiguration { get ; }
288292
@@ -337,21 +341,42 @@ internal bool PostgresCancellationPerformed
337341 internal NpgsqlConnector ( NpgsqlDataSource dataSource , NpgsqlConnection conn )
338342 : this ( dataSource )
339343 {
340- if ( conn . ProvideClientCertificatesCallback is not null )
341- ClientCertificatesCallback = certs => conn . ProvideClientCertificatesCallback ( certs ) ;
342- if ( conn . UserCertificateValidationCallback is not null )
343- UserCertificateValidationCallback = conn . UserCertificateValidationCallback ;
344-
344+ var sslClientAuthenticationOptionsCallback = conn . SslClientAuthenticationOptionsCallback ;
345345#pragma warning disable CS0618 // Obsolete
346+ var provideClientCertificatesCallback = conn . ProvideClientCertificatesCallback ;
347+ var userCertificateValidationCallback = conn . UserCertificateValidationCallback ;
348+ if ( provideClientCertificatesCallback is not null ||
349+ userCertificateValidationCallback is not null )
350+ {
351+ if ( sslClientAuthenticationOptionsCallback is not null )
352+ throw new NotSupportedException ( NpgsqlStrings . SslClientAuthenticationOptionsCallbackWithOtherCallbacksNotSupported ) ;
353+
354+ sslClientAuthenticationOptionsCallback = options =>
355+ {
356+ if ( provideClientCertificatesCallback is not null )
357+ {
358+ options . ClientCertificates ??= new X509Certificate2Collection ( ) ;
359+ provideClientCertificatesCallback . Invoke ( options . ClientCertificates ) ;
360+ }
361+
362+ if ( userCertificateValidationCallback is not null )
363+ {
364+ options . RemoteCertificateValidationCallback = userCertificateValidationCallback ;
365+ }
366+ } ;
367+ }
368+
369+ if ( sslClientAuthenticationOptionsCallback is not null )
370+ SslClientAuthenticationOptionsCallback = sslClientAuthenticationOptionsCallback ;
371+
346372 ProvidePasswordCallback = conn . ProvidePasswordCallback ;
347373#pragma warning restore CS0618
348374 }
349375
350376 NpgsqlConnector ( NpgsqlConnector connector )
351377 : this ( connector . DataSource )
352378 {
353- ClientCertificatesCallback = connector . ClientCertificatesCallback ;
354- UserCertificateValidationCallback = connector . UserCertificateValidationCallback ;
379+ SslClientAuthenticationOptionsCallback = connector . SslClientAuthenticationOptionsCallback ;
355380 ProvidePasswordCallback = connector . ProvidePasswordCallback ;
356381 }
357382
@@ -367,8 +392,7 @@ internal NpgsqlConnector(NpgsqlDataSource dataSource, NpgsqlConnection conn)
367392 TransactionLogger = LoggingConfiguration . TransactionLogger ;
368393 CopyLogger = LoggingConfiguration . CopyLogger ;
369394
370- ClientCertificatesCallback = dataSource . ClientCertificatesCallback ;
371- UserCertificateValidationCallback = dataSource . UserCertificateValidationCallback ;
395+ SslClientAuthenticationOptionsCallback = dataSource . SslClientAuthenticationOptionsCallback ;
372396
373397#if NET7_0_OR_GREATER
374398 NegotiateOptionsCallback = dataSource . Configuration . NegotiateOptionsCallback ;
@@ -777,7 +801,7 @@ async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, Cancellat
777801 throw new NpgsqlException ( "SSL connection requested. No SSL enabled connection from this host is configured." ) ;
778802 break ;
779803 case 'S' :
780- await DataSource . TransportSecurityHandler . NegotiateEncryption ( async , this , sslMode , timeout ) . ConfigureAwait( false) ;
804+ await DataSource . TransportSecurityHandler . NegotiateEncryption ( async , this , sslMode , timeout , cancellationToken ) . ConfigureAwait( false) ;
781805 break ;
782806 }
783807
@@ -802,7 +826,7 @@ async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, Cancellat
802826 }
803827 }
804828
805- internal async Task NegotiateEncryption( SslMode sslMode , NpgsqlTimeout timeout , bool async )
829+ internal async Task NegotiateEncryption( SslMode sslMode , NpgsqlTimeout timeout , bool async , CancellationToken cancellationToken )
806830 {
807831 var clientCertificates = new X509Certificate2Collection( ) ;
808832 var certPath = Settings. SslCertificate ?? PostgresEnvironment . SslCert ?? PostgresEnvironment . SslCertDefault ;
@@ -812,7 +836,7 @@ internal async Task NegotiateEncryption(SslMode sslMode, NpgsqlTimeout timeout,
812836 var password = Settings. SslPassword;
813837
814838 X509Certificate2? cert = null ;
815- if ( Path . GetExtension ( certPath ) . ToUpperInvariant ( ) != " . PFX" )
839+ if ( ! string . Equals ( Path . GetExtension ( certPath ) , ".pfx" , StringComparison . OrdinalIgnoreCase ) )
816840 {
817841 // It's PEM time
818842 var keyPath = Settings. SslKey ?? PostgresEnvironment. SslKey ?? PostgresEnvironment. SslKeyDefault;
@@ -836,28 +860,13 @@ internal async Task NegotiateEncryption(SslMode sslMode, NpgsqlTimeout timeout,
836860
837861 try
838862 {
839- ClientCertificatesCallback? . Invoke ( clientCertificates ) ;
840-
841863 var checkCertificateRevocation = Settings. CheckCertificateRevocation ;
842864
843865 RemoteCertificateValidationCallback? certificateValidationCallback ;
844866 X509Certificate2? caCert ;
845867 string ? certRootPath = null ;
846868
847- if ( UserCertificateValidationCallback is not null )
848- {
849- if ( sslMode is SslMode . VerifyCA or SslMode . VerifyFull )
850- throw new ArgumentException( string . Format ( NpgsqlStrings . CannotUseSslVerifyWithUserCallback , sslMode ) ) ;
851-
852- if ( Settings . RootCertificate is not null )
853- throw new ArgumentException( NpgsqlStrings . CannotUseSslRootCertificateWithUserCallback ) ;
854-
855- if ( DataSource . TransportSecurityHandler . RootCertificateCallback is not null )
856- throw new ArgumentException( NpgsqlStrings . CannotUseValidationRootCertificateCallbackWithUserCallback ) ;
857-
858- certificateValidationCallback = UserCertificateValidationCallback;
859- }
860- else if ( sslMode is SslMode . Prefer or SslMode . Require )
869+ if ( sslMode is SslMode . Prefer or SslMode . Require )
861870 {
862871 certificateValidationCallback = SslTrustServerValidation ;
863872 checkCertificateRevocation = false ;
@@ -892,19 +901,48 @@ internal async Task NegotiateEncryption(SslMode sslMode, NpgsqlTimeout timeout,
892901
893902 timeout. CheckAndApply ( this ) ;
894903
895- try
904+ var sslStream = new SslStream( _stream , leaveInnerStreamOpen : false ) ;
905+
906+ var sslStreamOptions = new SslClientAuthenticationOptions
907+ {
908+ TargetHost = host,
909+ ClientCertificates = clientCertificates,
910+ EnabledSslProtocols = SslProtocols. None ,
911+ CertificateRevocationCheckMode = checkCertificateRevocation ? X509RevocationMode . Online : X509RevocationMode . Offline ,
912+ RemoteCertificateValidationCallback = certificateValidationCallback
913+ } ;
914+
915+ if ( SslClientAuthenticationOptionsCallback is not null )
896916 {
897- var sslStream = new SslStream ( _stream , leaveInnerStreamOpen : false , certificateValidationCallback ) ;
917+ SslClientAuthenticationOptionsCallback . Invoke ( sslStreamOptions ) ;
898918
919+ // User changed remote certificate validation callback
920+ // Check whether the change doesn't lead to unexpected behavior
921+ if ( sslStreamOptions . RemoteCertificateValidationCallback != certificateValidationCallback )
922+ {
923+ if ( sslMode is SslMode . VerifyCA or SslMode . VerifyFull )
924+ throw new ArgumentException( string . Format ( NpgsqlStrings . CannotUseSslVerifyWithCustomValidationCallback , sslMode ) ) ;
925+
926+ if ( Settings . RootCertificate is not null )
927+ throw new ArgumentException( NpgsqlStrings . CannotUseSslRootCertificateWithCustomValidationCallback ) ;
928+
929+ if ( DataSource . TransportSecurityHandler . RootCertificateCallback is not null )
930+ throw new ArgumentException( NpgsqlStrings . CannotUseValidationRootCertificateCallbackWithCustomValidationCallback ) ;
931+ }
932+ }
933+
934+ try
935+ {
899936 if ( async)
900- await sslStream . AuthenticateAsClientAsync ( host , clientCertificates , SslProtocols . None , checkCertificateRevocation ) . ConfigureAwait ( false ) ;
937+ await sslStream . AuthenticateAsClientAsync ( sslStreamOptions , cancellationToken ) . ConfigureAwait ( false ) ;
901938 else
902- sslStream . AuthenticateAsClient ( host , clientCertificates , SslProtocols . None , checkCertificateRevocation ) ;
939+ sslStream . AuthenticateAsClient ( sslStreamOptions ) ;
903940
904941 _stream = sslStream ;
905942 }
906943 catch ( Exception e )
907944 {
945+ sslStream. Dispose ( ) ;
908946 throw new NpgsqlException( "Exception while performing SSL handshake" , e ) ;
909947 }
910948
0 commit comments