@@ -216,7 +216,7 @@ public async Task ConnectAsync(ConnectionSettings cs, IOBehavior ioBehavior, Can
216
216
authPluginName = initialHandshake . AuthPluginName ;
217
217
else
218
218
authPluginName = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . SecureConnection ) == 0 ? "mysql_old_password" : "mysql_native_password" ;
219
- if ( authPluginName != "mysql_native_password" && authPluginName != "sha256_password" )
219
+ if ( authPluginName != "mysql_native_password" && authPluginName != "sha256_password" && authPluginName != "caching_sha2_password" )
220
220
throw new NotSupportedException ( "Authentication method '{0}' is not supported." . FormatInvariant ( initialHandshake . AuthPluginName ) ) ;
221
221
222
222
ServerVersion = new ServerVersion ( Encoding . ASCII . GetString ( initialHandshake . ServerVersion ) ) ;
@@ -246,8 +246,7 @@ public async Task ConnectAsync(ConnectionSettings cs, IOBehavior ioBehavior, Can
246
246
// if server doesn't support the authentication fast path, it will send a new challenge
247
247
if ( payload . HeaderByte == AuthenticationMethodSwitchRequestPayload . Signature )
248
248
{
249
- await SwitchAuthenticationAsync ( cs , payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
250
- payload = await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
249
+ payload = await SwitchAuthenticationAsync ( cs , payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
251
250
}
252
251
253
252
OkPayload . Create ( payload ) ;
@@ -283,8 +282,7 @@ public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavio
283
282
payload = await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
284
283
if ( payload . HeaderByte == AuthenticationMethodSwitchRequestPayload . Signature )
285
284
{
286
- await SwitchAuthenticationAsync ( cs , payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
287
- payload = await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
285
+ payload = await SwitchAuthenticationAsync ( cs , payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
288
286
}
289
287
OkPayload . Create ( payload ) ;
290
288
}
@@ -301,7 +299,7 @@ public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavio
301
299
return false ;
302
300
}
303
301
304
- private async Task SwitchAuthenticationAsync ( ConnectionSettings cs , PayloadData payload , IOBehavior ioBehavior , CancellationToken cancellationToken )
302
+ private async Task < PayloadData > SwitchAuthenticationAsync ( ConnectionSettings cs , PayloadData payload , IOBehavior ioBehavior , CancellationToken cancellationToken )
305
303
{
306
304
// if the server didn't support the hashed password; rehash with the new challenge
307
305
var switchRequest = AuthenticationMethodSwitchRequestPayload . Create ( payload ) ;
@@ -312,82 +310,57 @@ private async Task SwitchAuthenticationAsync(ConnectionSettings cs, PayloadData
312
310
var hashedPassword = AuthenticationUtility . CreateAuthenticationResponse ( AuthPluginData , 0 , cs . Password ) ;
313
311
payload = new PayloadData ( new ArraySegment < byte > ( hashedPassword ) ) ;
314
312
await SendReplyAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
315
- break ;
313
+ return await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
316
314
317
315
case "mysql_clear_password" :
318
316
if ( ! m_isSecureConnection )
319
317
throw new MySqlException ( "Authentication method '{0}' requires a secure connection." . FormatInvariant ( switchRequest . Name ) ) ;
320
318
payload = new PayloadData ( new ArraySegment < byte > ( Encoding . UTF8 . GetBytes ( cs . Password ) ) ) ;
321
319
await SendReplyAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
322
- break ;
320
+ return await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
323
321
324
- case "sha256_password" :
325
- // add NUL terminator to password
326
- var passwordBytes = Encoding . UTF8 . GetBytes ( cs . Password ) ;
327
- Array . Resize ( ref passwordBytes , passwordBytes . Length + 1 ) ;
322
+ case "caching_sha2_password" :
323
+ var scrambleBytes = AuthenticationUtility . CreateScrambleResponse ( Utility . TrimZeroByte ( switchRequest . Data ) , cs . Password ) ;
324
+ payload = new PayloadData ( new ArraySegment < byte > ( scrambleBytes ) ) ;
325
+ await SendReplyAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
326
+ payload = await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
327
+
328
+ var cachingSha2ServerResponsePayload = CachingSha2ServerResponsePayload . Create ( payload ) ;
328
329
329
- if ( ! m_isSecureConnection && passwordBytes . Length > 1 )
330
+ if ( cachingSha2ServerResponsePayload . Succeeded )
331
+ {
332
+ return await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
333
+ }
334
+
335
+ if ( ! m_isSecureConnection && cs . Password . Length > 1 )
330
336
{
331
337
#if NET45
332
338
throw new MySqlException ( "Authentication method '{0}' requires a secure connection (prior to .NET 4.6)." . FormatInvariant ( switchRequest . Name ) ) ;
333
339
#else
334
- string publicKey ;
335
- if ( ! string . IsNullOrEmpty ( cs . ServerRsaPublicKeyFile ) )
336
- {
337
- try
338
- {
339
- publicKey = File . ReadAllText ( cs . ServerRsaPublicKeyFile ) ;
340
- }
341
- catch ( IOException ex )
342
- {
343
- throw new MySqlException ( "Couldn't load server's RSA public key from '{0}'" . FormatInvariant ( cs . ServerRsaPublicKeyFile ) , ex ) ;
344
- }
345
- }
346
- else if ( cs . AllowPublicKeyRetrieval )
347
- {
348
- // request the RSA public key
349
- await SendReplyAsync ( new PayloadData ( new ArraySegment < byte > ( new byte [ ] { 0x01 } , 0 , 1 ) ) , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
350
- payload = await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
351
- var publicKeyPayload = AuthenticationMoreDataPayload . Create ( payload ) ;
352
- publicKey = Encoding . ASCII . GetString ( publicKeyPayload . Data ) ;
353
- }
354
- else
355
- {
356
- throw new MySqlException ( "Authentication method '{0}' failed. Either use a secure connection, specify the server's RSA public key with ServerRSAPublicKeyFile, or set AllowPublicKeyRetrieval=True." . FormatInvariant ( switchRequest . Name ) ) ;
357
- }
358
340
359
- // load the RSA public key
360
- RSA rsa ;
361
- try
362
- {
363
- rsa = Utility . DecodeX509PublicKey ( publicKey ) ;
364
- }
365
- catch ( Exception ex )
366
- {
367
- throw new MySqlException ( "Couldn't load server's RSA public key; try using a secure connection instead." , ex ) ;
368
- }
341
+ var rsaPublicKey = await GetRsaPublicKeyForCachingSha2PasswordAsync ( switchRequest . Name , cs , ioBehavior , cancellationToken ) ;
342
+ return await SendEncryptedPasswordAsync ( rsaPublicKey , RSAEncryptionPadding . Pkcs1 , cs , ioBehavior , switchRequest , cancellationToken ) ;
343
+ #endif
344
+ }
345
+ else
346
+ {
347
+ return await SendClearPasswordAsync ( cs , ioBehavior , cancellationToken ) ;
348
+ }
369
349
370
- using ( rsa )
371
- {
372
- // XOR the password bytes with the challenge
373
- AuthPluginData = Utility . TrimZeroByte ( switchRequest . Data ) ;
374
- for ( int i = 0 ; i < passwordBytes . Length ; i ++ )
375
- passwordBytes [ i ] ^= AuthPluginData [ i % AuthPluginData . Length ] ;
376
-
377
- // encrypt with RSA public key
378
- var encryptedPassword = rsa . Encrypt ( passwordBytes , RSAEncryptionPadding . OaepSHA1 ) ;
379
- payload = new PayloadData ( new ArraySegment < byte > ( encryptedPassword ) ) ;
380
- await SendReplyAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
381
- }
350
+ case "sha256_password" :
351
+ if ( ! m_isSecureConnection && cs . Password . Length > 1 )
352
+ {
353
+ #if NET45
354
+ throw new MySqlException ( "Authentication method '{0}' requires a secure connection (prior to .NET 4.6)." . FormatInvariant ( switchRequest . Name ) ) ;
355
+ #else
356
+ var publicKey = await GetRsaPublicKeyForSha256PasswordAsync ( switchRequest . Name , cs , ioBehavior , cancellationToken ) ;
357
+ return await SendEncryptedPasswordAsync ( publicKey , RSAEncryptionPadding . OaepSHA1 , cs , ioBehavior , switchRequest , cancellationToken ) ;
382
358
#endif
383
359
}
384
360
else
385
361
{
386
- // send plaintext password
387
- payload = new PayloadData ( new ArraySegment < byte > ( passwordBytes ) ) ;
388
- await SendReplyAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
362
+ return await SendClearPasswordAsync ( cs , ioBehavior , cancellationToken ) ;
389
363
}
390
- break ;
391
364
392
365
case "mysql_old_password" :
393
366
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." ) ;
@@ -397,6 +370,126 @@ private async Task SwitchAuthenticationAsync(ConnectionSettings cs, PayloadData
397
370
}
398
371
}
399
372
373
+ private async Task < PayloadData > SendClearPasswordAsync ( ConnectionSettings cs , IOBehavior ioBehavior , CancellationToken cancellationToken )
374
+ {
375
+ // add NUL terminator to password
376
+ var passwordBytes = Encoding . UTF8 . GetBytes ( cs . Password ) ;
377
+ Array . Resize ( ref passwordBytes , passwordBytes . Length + 1 ) ;
378
+
379
+ // send plaintext password
380
+ var payload = new PayloadData ( new ArraySegment < byte > ( passwordBytes ) ) ;
381
+ await SendReplyAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
382
+ return await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
383
+ }
384
+
385
+ #if ! NET45
386
+ private async Task < PayloadData > SendEncryptedPasswordAsync (
387
+ string rsaPublicKey ,
388
+ RSAEncryptionPadding rsaEncryptionPadding ,
389
+ ConnectionSettings cs ,
390
+ IOBehavior ioBehavior ,
391
+ AuthenticationMethodSwitchRequestPayload switchRequest ,
392
+ CancellationToken cancellationToken )
393
+ {
394
+ // load the RSA public key
395
+ RSA rsa ;
396
+ try
397
+ {
398
+ rsa = Utility . DecodeX509PublicKey ( rsaPublicKey ) ;
399
+ }
400
+ catch ( Exception ex )
401
+ {
402
+ throw new MySqlException ( "Couldn't load server's RSA public key; try using a secure connection instead." , ex ) ;
403
+ }
404
+
405
+ // add NUL terminator to password
406
+ var passwordBytes = Encoding . UTF8 . GetBytes ( cs . Password ) ;
407
+ Array . Resize ( ref passwordBytes , passwordBytes . Length + 1 ) ;
408
+
409
+ using ( rsa )
410
+ {
411
+ // XOR the password bytes with the challenge
412
+ AuthPluginData = Utility . TrimZeroByte ( switchRequest . Data ) ;
413
+ for ( var i = 0 ; i < passwordBytes . Length ; i ++ )
414
+ passwordBytes [ i ] ^= AuthPluginData [ i % AuthPluginData . Length ] ;
415
+
416
+ // encrypt with RSA public key
417
+ var encryptedPassword = rsa . Encrypt ( passwordBytes , rsaEncryptionPadding ) ;
418
+ var payload = new PayloadData ( new ArraySegment < byte > ( encryptedPassword ) ) ;
419
+ await SendReplyAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
420
+ return await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
421
+ }
422
+ }
423
+ #endif
424
+
425
+ private async Task < string > GetRsaPublicKeyForSha256PasswordAsync (
426
+ string switchRequestName ,
427
+ ConnectionSettings cs ,
428
+ IOBehavior ioBehavior ,
429
+ CancellationToken cancellationToken )
430
+ {
431
+ if ( ! string . IsNullOrEmpty ( cs . ServerRsaPublicKeyFile ) )
432
+ {
433
+ try
434
+ {
435
+ return File . ReadAllText ( cs . ServerRsaPublicKeyFile ) ;
436
+ }
437
+ catch ( IOException ex )
438
+ {
439
+ throw new MySqlException (
440
+ "Couldn't load server's RSA public key from '{0}'" . FormatInvariant ( cs . ServerRsaPublicKeyFile ) , ex ) ;
441
+ }
442
+ }
443
+
444
+ if ( cs . AllowPublicKeyRetrieval )
445
+ {
446
+ // request the RSA public key
447
+ await SendReplyAsync ( new PayloadData ( new ArraySegment < byte > ( new byte [ ] { 0x01 } , 0 , 1 ) ) , ioBehavior ,
448
+ cancellationToken ) . ConfigureAwait ( false ) ;
449
+ var payload = await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
450
+ var publicKeyPayload = AuthenticationMoreDataPayload . Create ( payload ) ;
451
+ return Encoding . ASCII . GetString ( publicKeyPayload . Data ) ;
452
+ }
453
+
454
+ throw new MySqlException (
455
+ "Authentication method '{0}' failed. Either use a secure connection, specify the server's RSA public key with ServerRSAPublicKeyFile, or set AllowPublicKeyRetrieval=True."
456
+ . FormatInvariant ( switchRequestName ) ) ;
457
+ }
458
+
459
+ private async Task < string > GetRsaPublicKeyForCachingSha2PasswordAsync (
460
+ string switchRequestName ,
461
+ ConnectionSettings cs ,
462
+ IOBehavior ioBehavior ,
463
+ CancellationToken cancellationToken )
464
+ {
465
+ if ( ! string . IsNullOrEmpty ( cs . ServerRsaPublicKeyFile ) )
466
+ {
467
+ try
468
+ {
469
+ return File . ReadAllText ( cs . ServerRsaPublicKeyFile ) ;
470
+ }
471
+ catch ( IOException ex )
472
+ {
473
+ throw new MySqlException (
474
+ "Couldn't load server's RSA public key from '{0}'" . FormatInvariant ( cs . ServerRsaPublicKeyFile ) , ex ) ;
475
+ }
476
+ }
477
+
478
+ if ( cs . AllowPublicKeyRetrieval )
479
+ {
480
+ // request the RSA public key
481
+ await SendReplyAsync ( new PayloadData ( new ArraySegment < byte > ( new byte [ ] { 0x02 } , 0 , 1 ) ) , ioBehavior ,
482
+ cancellationToken ) . ConfigureAwait ( false ) ;
483
+ var payload = await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
484
+ var publicKeyPayload = AuthenticationMoreDataPayload . Create ( payload ) ;
485
+ return Encoding . ASCII . GetString ( publicKeyPayload . Data ) ;
486
+ }
487
+
488
+ throw new MySqlException (
489
+ "Authentication method '{0}' failed. Either use a secure connection, specify the server's RSA public key with ServerRSAPublicKeyFile, or set AllowPublicKeyRetrieval=True."
490
+ . FormatInvariant ( switchRequestName ) ) ;
491
+ }
492
+
400
493
public async Task < bool > TryPingAsync ( IOBehavior ioBehavior , CancellationToken cancellationToken )
401
494
{
402
495
VerifyState ( State . Connected ) ;
0 commit comments