@@ -283,7 +283,7 @@ internal bool PostgresCancellationPerformed
283283 internal bool AttemptPostgresCancellation { get ; private set ; }
284284 static readonly TimeSpan _cancelImmediatelyTimeout = TimeSpan . FromMilliseconds ( - 1 ) ;
285285
286- X509Certificate2 ? _certificate ;
286+ IDisposable ? _certificate ;
287287
288288 internal NpgsqlLoggingConfiguration LoggingConfiguration { get ; }
289289
@@ -786,8 +786,12 @@ async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, Cancellat
786786
787787 IsSecure = false ;
788788
789- if ( sslMode is SslMode . Prefer or SslMode . Require or SslMode . VerifyCA or SslMode . VerifyFull )
789+ if ( ( sslMode is SslMode . Prefer && DataSource . EncryptionNegotiator is not null ) ||
790+ sslMode is SslMode . Require or SslMode . VerifyCA or SslMode . VerifyFull )
790791 {
792+ if ( DataSource . EncryptionNegotiator is null )
793+ throw new InvalidOperationException( NpgsqlStrings . EncryptionDisabled ) ;
794+
791795 WriteSslRequest( ) ;
792796 await Flush ( async , cancellationToken ) ;
793797
@@ -804,136 +808,152 @@ async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, Cancellat
804808 throw new NpgsqlException ( "SSL connection requested. No SSL enabled connection from this host is configured." ) ;
805809 break ;
806810 case 'S' :
807- var clientCertificates = new X509Certificate2Collection ( ) ;
808- var certPath = Settings. SslCertificate ?? PostgresEnvironment. SslCert ?? PostgresEnvironment. SslCertDefault;
811+ await DataSource . EncryptionNegotiator ( this , sslMode , timeout , async, isFirstAttempt ) ;
812+ break ;
813+ }
809814
810- if ( certPath ! = null )
811- {
812- var password = Settings . SslPassword ;
815+ if ( ReadBuffer . ReadBytesLeft > 0 )
816+ throw new NpgsqlException ( "Additional unencrypted data received after SSL negotiation - this should never happen, and may be an indication of a man-in-the-middle attack." ) ;
817+ }
813818
814- if ( Path . GetExtension ( certPath ) . ToUpperInvariant ( ) != ". PFX")
815- {
819+ ConnectionLogger. LogTrace ( "Socket connected to {Host}:{Port}" , Host , Port ) ;
820+ }
821+ catch
822+ {
823+ _stream? . Dispose ( ) ;
824+ _stream = null ! ;
825+
826+ _baseStream? . Dispose ( ) ;
827+ _baseStream = null ! ;
828+
829+ _socket? . Dispose ( ) ;
830+ _socket = null ! ;
831+
832+ throw ;
833+ }
834+ }
835+
836+ internal async Task NegotiateEncryption( SslMode sslMode , NpgsqlTimeout timeout , bool async , bool isFirstAttempt )
837+ {
838+ var clientCertificates = new X509Certificate2Collection( ) ;
839+ var certPath = Settings. SslCertificate ?? PostgresEnvironment . SslCert ?? PostgresEnvironment . SslCertDefault ;
840+
841+ if ( certPath != null )
842+ {
843+ var password = Settings. SslPassword;
844+
845+ X509Certificate2? cert = null ;
846+ if ( Path . GetExtension ( certPath ) . ToUpperInvariant ( ) != ". PFX")
847+ {
816848#if NET5_0_OR_GREATER
817- // It's PEM time
818- var keyPath = Settings . SslKey ?? PostgresEnvironment . SslKey ?? PostgresEnvironment . SslKeyDefault ;
819- _certificate = string . IsNullOrEmpty ( password )
820- ? X509Certificate2 . CreateFromPemFile ( certPath , keyPath )
821- : X509Certificate2 . CreateFromEncryptedPemFile ( certPath , password , keyPath ) ;
822- if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
823- {
824- // Windows crypto API has a bug with pem certs
825- // See #3650
826- using var previousCert = _certificate;
827- _certificate = new X509Certificate2 ( _certificate . Export ( X509ContentType . Pkcs12 ) ) ;
828- }
849+ // It's PEM time
850+ var keyPath = Settings . SslKey ?? PostgresEnvironment . SslKey ?? PostgresEnvironment . SslKeyDefault ;
851+ cert = string . IsNullOrEmpty ( password )
852+ ? X509Certificate2 . CreateFromPemFile ( certPath , keyPath )
853+ : X509Certificate2 . CreateFromEncryptedPemFile ( certPath , password , keyPath ) ;
854+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
855+ {
856+ // Windows crypto API has a bug with pem certs
857+ // See #3650
858+ using var previousCert = cert;
859+ cert = new X509Certificate2 ( cert . Export ( X509ContentType . Pkcs12 ) ) ;
860+ }
861+
829862#else
830- // Technically PEM certificates are supported as of .NET 5 but we don't build for the net5.0
831- // TFM anymore since .NET 5 is out of support
832- // This is a breaking change for .NET 5 as of Npgsql 8!
833- throw new NotSupportedException ( "PEM certificates are only supported with .NET 6 and higher" ) ;
863+ // Technically PEM certificates are supported as of .NET 5 but we don't build for the net5.0
864+ // TFM anymore since .NET 5 is out of support
865+ // This is a breaking change for .NET 5 as of Npgsql 8!
866+ throw new NotSupportedException ( "PEM certificates are only supported with .NET 6 and higher" ) ;
834867#endif
835- }
868+ }
836869
837- _certificate ??= new X509Certificate2 ( certPath , password ) ;
838- clientCertificates. Add ( _certificate ) ;
839- }
870+ cert ??= new X509Certificate2 ( certPath , password ) ;
871+ clientCertificates. Add ( cert ) ;
840872
841- ClientCertificatesCallback? . Invoke ( clientCertificates ) ;
873+ _certificate = cert;
874+ }
842875
843- var checkCertificateRevocation = Settings. CheckCertificateRevocation ;
876+ try
877+ {
878+ ClientCertificatesCallback ? . Invoke ( clientCertificates ) ;
844879
845- RemoteCertificateValidationCallback? certificateValidationCallback ;
846- X509Certificate2? caCert ;
847- string ? certRootPath = null ;
880+ var checkCertificateRevocation = Settings. CheckCertificateRevocation;
848881
849- if ( UserCertificateValidationCallback is not null )
850- {
851- if ( sslMode is SslMode . VerifyCA or SslMode . VerifyFull )
852- throw new ArgumentException( string . Format ( NpgsqlStrings . CannotUseSslVerifyWithUserCallback , sslMode ) ) ;
882+ RemoteCertificateValidationCallback? certificateValidationCallback;
883+ X509Certificate2? caCert;
884+ string ? certRootPath = null ;
853885
854- if ( Settings . RootCertificate is not null )
855- throw new ArgumentException( NpgsqlStrings . CannotUseSslRootCertificateWithUserCallback ) ;
886+ if ( UserCertificateValidationCallback is not null )
887+ {
888+ if ( sslMode is SslMode . VerifyCA or SslMode . VerifyFull )
889+ throw new ArgumentException( string . Format ( NpgsqlStrings . CannotUseSslVerifyWithUserCallback , sslMode ) ) ;
856890
857- if ( DataSource . RootCertificateCallback is not null )
858- throw new ArgumentException( NpgsqlStrings . CannotUseValidationRootCertificateCallbackWithUserCallback ) ;
891+ if ( Settings . RootCertificate is not null )
892+ throw new ArgumentException( NpgsqlStrings . CannotUseSslRootCertificateWithUserCallback ) ;
859893
860- certificateValidationCallback = UserCertificateValidationCallback;
861- }
862- else if ( sslMode is SslMode . Prefer or SslMode . Require )
863- {
864- if ( isFirstAttempt && sslMode is SslMode . Require && ! Settings . TrustServerCertificate )
865- throw new ArgumentException( NpgsqlStrings . CannotUseSslModeRequireWithoutTrustServerCertificate ) ;
894+ if ( DataSource . RootCertificateCallback is not null )
895+ throw new ArgumentException( NpgsqlStrings . CannotUseValidationRootCertificateCallbackWithUserCallback ) ;
866896
867- certificateValidationCallback = SslTrustServerValidation;
868- checkCertificateRevocation = false;
869- }
870- else if ( ( caCert = DataSource . RootCertificateCallback ? . Invoke ( ) ) is not null ||
871- ( certRootPath = Settings . RootCertificate ??
872- PostgresEnvironment . SslCertRoot ?? PostgresEnvironment . SslCertRootDefault ) is not null )
873- {
874- certificateValidationCallback = SslRootValidation( sslMode == SslMode . VerifyFull , certRootPath , caCert ) ;
875- }
876- else if ( sslMode == SslMode . VerifyCA )
877- {
878- certificateValidationCallback = SslVerifyCAValidation;
879- }
880- else
881- {
882- Debug. Assert ( sslMode == SslMode . VerifyFull ) ;
883- certificateValidationCallback = SslVerifyFullValidation;
884- }
897+ certificateValidationCallback = UserCertificateValidationCallback;
898+ }
899+ else if ( sslMode is SslMode . Prefer or SslMode . Require )
900+ {
901+ if ( isFirstAttempt && sslMode is SslMode . Require && ! Settings . TrustServerCertificate )
902+ throw new ArgumentException( NpgsqlStrings . CannotUseSslModeRequireWithoutTrustServerCertificate ) ;
903+
904+ certificateValidationCallback = SslTrustServerValidation;
905+ checkCertificateRevocation = false;
906+ }
907+ else if ( ( caCert = DataSource . RootCertificateCallback ? . Invoke ( ) ) is not null ||
908+ ( certRootPath = Settings . RootCertificate ??
909+ PostgresEnvironment . SslCertRoot ?? PostgresEnvironment . SslCertRootDefault ) is not null )
910+ {
911+ certificateValidationCallback = SslRootValidation( sslMode == SslMode . VerifyFull , certRootPath , caCert ) ;
912+ }
913+ else if ( sslMode == SslMode . VerifyCA )
914+ {
915+ certificateValidationCallback = SslVerifyCAValidation;
916+ }
917+ else
918+ {
919+ Debug. Assert ( sslMode == SslMode . VerifyFull ) ;
920+ certificateValidationCallback = SslVerifyFullValidation;
921+ }
885922
886- timeout. CheckAndApply ( this ) ;
923+ timeout. CheckAndApply ( this ) ;
887924
888- try
889- {
890- var sslStream = new SslStream( _stream , leaveInnerStreamOpen : false , certificateValidationCallback ) ;
925+ try
926+ {
927+ var sslStream = new SslStream( _stream , leaveInnerStreamOpen : false , certificateValidationCallback ) ;
891928
892- var sslProtocols = SslProtocols. None ;
929+ var sslProtocols = SslProtocols. None ;
893930#if NETSTANDARD2_0
894- // On .NET Framework SslProtocols.None can be disabled, see #3718
895- sslProtocols = SslProtocols . Tls | SslProtocols . Tls11 | SslProtocols . Tls12 ;
931+ // On .NET Framework SslProtocols.None can be disabled, see #3718
932+ sslProtocols = SslProtocols . Tls | SslProtocols . Tls11 | SslProtocols . Tls12 ;
896933#endif
897934
898- if ( async)
899- await sslStream . AuthenticateAsClientAsync ( Host , clientCertificates , sslProtocols , checkCertificateRevocation ) ;
900- else
901- sslStream . AuthenticateAsClient ( Host , clientCertificates , sslProtocols , checkCertificateRevocation ) ;
902-
903- _stream = sslStream ;
904- }
905- catch ( Exception e )
906- {
907- throw new NpgsqlException ( "Exception while performing SSL handshake" , e ) ;
908- }
909-
910- ReadBuffer . Underlying = _stream ;
911- WriteBuffer . Underlying = _stream ;
912- IsSecure = true ;
913- ConnectionLogger . LogTrace ( "SSL negotiation successful" ) ;
914- break ;
915- }
935+ if ( async)
936+ await sslStream . AuthenticateAsClientAsync ( Host , clientCertificates , sslProtocols , checkCertificateRevocation ) ;
937+ else
938+ sslStream . AuthenticateAsClient ( Host , clientCertificates , sslProtocols , checkCertificateRevocation ) ;
916939
917- if ( ReadBuffer . ReadBytesLeft > 0 )
918- throw new NpgsqlException ( "Additional unencrypted data received after SSL negotiation - this should never happen, and may be an indication of a man-in-the-middle attack." ) ;
940+ _stream = sslStream ;
941+ }
942+ catch ( Exception e )
943+ {
944+ throw new NpgsqlException ( "Exception while performing SSL handshake" , e ) ;
919945 }
920946
921- ConnectionLogger. LogTrace ( "Socket connected to {Host}:{Port}" , Host , Port ) ;
947+ ReadBuffer . Underlying = _stream ;
948+ WriteBuffer . Underlying = _stream ;
949+ IsSecure = true ;
950+ ConnectionLogger . LogTrace ( "SSL negotiation successful" ) ;
922951 }
923952 catch
924953 {
925954 _certificate ? . Dispose ( ) ;
926955 _certificate = null ;
927956
928- _stream? . Dispose ( ) ;
929- _stream = null ! ;
930-
931- _baseStream? . Dispose ( ) ;
932- _baseStream = null ! ;
933-
934- _socket? . Dispose ( ) ;
935- _socket = null ! ;
936-
937957 throw ;
938958 }
939959 }
0 commit comments