@@ -448,13 +448,13 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
448
448
var initialHandshake = InitialHandshakePayload . Create ( payload . Span ) ;
449
449
450
450
// if PluginAuth is supported, then use the specified auth plugin; else, fall back to protocol capabilities to determine the auth type to use
451
- m_currentAuthenticationMethod = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . PluginAuth ) != 0 ? initialHandshake . AuthPluginName ! :
451
+ var currentAuthenticationMethod = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . PluginAuth ) != 0 ? initialHandshake . AuthPluginName ! :
452
452
( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . SecureConnection ) == 0 ? "mysql_old_password" :
453
453
"mysql_native_password" ;
454
- Log . ServerSentAuthPluginName ( m_logger , Id , m_currentAuthenticationMethod ) ;
455
- if ( m_currentAuthenticationMethod is not "mysql_native_password" and not "sha256_password" and not "caching_sha2_password" )
454
+ Log . ServerSentAuthPluginName ( m_logger , Id , currentAuthenticationMethod ) ;
455
+ if ( currentAuthenticationMethod is not "mysql_native_password" and not "sha256_password" and not "caching_sha2_password" )
456
456
{
457
- Log . UnsupportedAuthenticationMethod ( m_logger , Id , m_currentAuthenticationMethod ) ;
457
+ Log . UnsupportedAuthenticationMethod ( m_logger , Id , currentAuthenticationMethod ) ;
458
458
throw new NotSupportedException ( $ "Authentication method '{ initialHandshake . AuthPluginName } ' is not supported.") ;
459
459
}
460
460
@@ -560,7 +560,7 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
560
560
// there is no shared secret that can be used to validate the certificate
561
561
Log . CertificateErrorNoPassword ( m_logger , Id , m_sslPolicyErrors ) ;
562
562
}
563
- else if ( ValidateFingerprint ( ok . StatusInfo , initialHandshake . AuthPluginData . AsSpan ( 0 , 20 ) , password ) )
563
+ else if ( ValidateFingerprint ( ok . StatusInfo , initialHandshake . AuthPluginData . AsSpan ( 0 , 20 ) ) )
564
564
{
565
565
Log . CertificateErrorValidThumbprint ( m_logger , Id , m_sslPolicyErrors ) ;
566
566
ignoreCertificateError = true ;
@@ -626,36 +626,20 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
626
626
/// </summary>
627
627
/// <param name="validationHash">The validation hash received from the server.</param>
628
628
/// <param name="challenge">The auth plugin data from the initial handshake.</param>
629
- /// <param name="password">The user's password.</param>
630
629
/// <returns><c>true</c> if the validation hash matches the locally-computed value; otherwise, <c>false</c>.</returns>
631
- private bool ValidateFingerprint ( byte [ ] ? validationHash , ReadOnlySpan < byte > challenge , string password )
630
+ private bool ValidateFingerprint ( byte [ ] ? validationHash , ReadOnlySpan < byte > challenge )
632
631
{
633
632
// expect 0x01 followed by 64 hex characters giving a SHA2 hash
634
633
if ( validationHash ? . Length != 65 || validationHash [ 0 ] != 1 )
635
634
return false ;
636
635
637
- byte [ ] ? passwordHashResult = null ;
638
- switch ( m_currentAuthenticationMethod )
639
- {
640
- case "mysql_native_password" :
641
- passwordHashResult = AuthenticationUtility . HashPassword ( [ ] , password , onlyHashPassword : true ) ;
642
- break ;
643
-
644
- case "client_ed25519" :
645
- AuthenticationPlugins . TryGetPlugin ( m_currentAuthenticationMethod , out var ed25519Plugin ) ;
646
- if ( ed25519Plugin is IAuthenticationPlugin2 plugin2 )
647
- passwordHashResult = plugin2 . CreatePasswordHash ( password , challenge ) ;
648
- break ;
649
- }
650
- if ( passwordHashResult is null )
636
+ // the authentication plugin must have provided a password hash (via IAuthenticationPlugin3) that we saved for future use
637
+ if ( m_passwordHash is null )
651
638
return false ;
652
639
653
- Span < byte > combined = stackalloc byte [ 32 + challenge . Length + passwordHashResult . Length ] ;
654
- passwordHashResult . CopyTo ( combined ) ;
655
- challenge . CopyTo ( combined [ passwordHashResult . Length ..] ) ;
656
- m_remoteCertificateSha2Thumbprint ! . CopyTo ( combined [ ( passwordHashResult . Length + challenge . Length ) ..] ) ;
657
-
640
+ // hash password hash || scramble || certificate thumbprint
658
641
Span < byte > hashBytes = stackalloc byte [ 32 ] ;
642
+ Span < byte > combined = [ .. m_passwordHash , .. challenge , .. m_remoteCertificateSha2Thumbprint ! ] ;
659
643
#if NET5_0_OR_GREATER
660
644
SHA256 . TryHashData ( combined , hashBytes , out _ ) ;
661
645
#else
@@ -849,13 +833,12 @@ private async Task<PayloadData> SwitchAuthenticationAsync(ConnectionSettings cs,
849
833
// if the server didn't support the hashed password; rehash with the new challenge
850
834
var switchRequest = AuthenticationMethodSwitchRequestPayload . Create ( payload . Span ) ;
851
835
Log . SwitchingToAuthenticationMethod ( m_logger , Id , switchRequest . Name ) ;
852
- m_currentAuthenticationMethod = switchRequest . Name ;
853
836
switch ( switchRequest . Name )
854
837
{
855
838
case "mysql_native_password" :
856
839
AuthPluginData = switchRequest . Data ;
857
- var hashedPassword = AuthenticationUtility . CreateAuthenticationResponse ( AuthPluginData , password ) ;
858
- payload = new ( hashedPassword ) ;
840
+ AuthenticationUtility . CreateResponseAndPasswordHash ( password , AuthPluginData , out var nativeResponse , out m_passwordHash ) ;
841
+ payload = new ( nativeResponse ) ;
859
842
await SendReplyAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
860
843
return await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
861
844
@@ -908,14 +891,15 @@ private async Task<PayloadData> SwitchAuthenticationAsync(ConnectionSettings cs,
908
891
throw new NotSupportedException ( "'MySQL Server is requesting the insecure pre-4.1 auth mechanism (mysql_old_password). The user password must be upgraded; see https://dev.mysql.com/doc/refman/5.7/en/account-upgrades.html." ) ;
909
892
910
893
case "client_ed25519" :
911
- if ( ! AuthenticationPlugins . TryGetPlugin ( switchRequest . Name , out var ed25519Plugin ) )
894
+ if ( ! AuthenticationPlugins . TryGetPlugin ( switchRequest . Name , out var ed25519Plugin ) || ed25519Plugin is not IAuthenticationPlugin3 ed25519Plugin3 )
912
895
throw new NotSupportedException ( "You must install the MySqlConnector.Authentication.Ed25519 package and call Ed25519AuthenticationPlugin.Install to use client_ed25519 authentication." ) ;
913
- payload = new ( ed25519Plugin . CreateResponse ( password , switchRequest . Data ) ) ;
896
+ ed25519Plugin3 . CreateResponseAndPasswordHash ( password , switchRequest . Data , out var ed25519Response , out m_passwordHash ) ;
897
+ payload = new ( ed25519Response ) ;
914
898
await SendReplyAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
915
899
return await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
916
900
917
901
case "parsec" :
918
- if ( ! AuthenticationPlugins . TryGetPlugin ( switchRequest . Name , out var parsecPlugin ) )
902
+ if ( ! AuthenticationPlugins . TryGetPlugin ( switchRequest . Name , out var parsecPlugin ) || parsecPlugin is not IAuthenticationPlugin3 parsecPlugin3 )
919
903
throw new NotSupportedException ( "You must install the MySqlConnector.Authentication.Ed25519 package and call ParsecAuthenticationPlugin.Install to use parsec authentication." ) ;
920
904
payload = new ( [ ] ) ;
921
905
await SendReplyAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
@@ -925,7 +909,8 @@ private async Task<PayloadData> SwitchAuthenticationAsync(ConnectionSettings cs,
925
909
switchRequest . Data . CopyTo ( combinedData ) ;
926
910
payload . Span . CopyTo ( combinedData . Slice ( switchRequest . Data . Length ) ) ;
927
911
928
- payload = new ( parsecPlugin . CreateResponse ( password , combinedData ) ) ;
912
+ parsecPlugin3 . CreateResponseAndPasswordHash ( password , combinedData , out var parsecResponse , out m_passwordHash ) ;
913
+ payload = new ( parsecResponse ) ;
929
914
await SendReplyAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
930
915
return await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
931
916
@@ -2192,7 +2177,7 @@ protected override void OnStatementBegin(int index)
2192
2177
private PayloadData m_setNamesPayload ;
2193
2178
private byte [ ] ? m_pipelinedResetConnectionBytes ;
2194
2179
private Dictionary < string , PreparedStatements > ? m_preparedStatements ;
2195
- private string ? m_currentAuthenticationMethod ;
2180
+ private byte [ ] ? m_passwordHash ;
2196
2181
private byte [ ] ? m_remoteCertificateSha2Thumbprint ;
2197
2182
private SslPolicyErrors m_sslPolicyErrors ;
2198
2183
}
0 commit comments