From 83aa38c43935e7116cd387ce0330e6ca5433bf3e Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Sat, 14 Dec 2024 15:50:21 +0100 Subject: [PATCH] Extend AuthenticationFailed exception message. --- src/Tmds.Ssh/AuthResult.cs | 3 +- src/Tmds.Ssh/SshSession.Authentication.cs | 28 +++++++++++++++++-- .../UserAuthentication.PasswordAuth.cs | 19 +++++++------ .../UserAuthentication.PublicKeyAuth.cs | 7 +++-- 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/Tmds.Ssh/AuthResult.cs b/src/Tmds.Ssh/AuthResult.cs index fd8e9b0..22b7752 100644 --- a/src/Tmds.Ssh/AuthResult.cs +++ b/src/Tmds.Ssh/AuthResult.cs @@ -7,5 +7,6 @@ enum AuthResult { Failure, Success, - Partial + Partial, + Skipped } \ No newline at end of file diff --git a/src/Tmds.Ssh/SshSession.Authentication.cs b/src/Tmds.Ssh/SshSession.Authentication.cs index a9492df..f54ea6a 100644 --- a/src/Tmds.Ssh/SshSession.Authentication.cs +++ b/src/Tmds.Ssh/SshSession.Authentication.cs @@ -26,6 +26,10 @@ private async Task AuthenticateAsync(SshConnection connection, CancellationToken UserAuthContext context = new UserAuthContext(connection, _settings.UserName, _settings.PublicKeyAcceptedAlgorithms, _settings.MinimumRSAKeySize, Logger); + HashSet? rejectedMethods = null; + HashSet? failedMethods = null; + HashSet? skippedMethods = null; + int partialAuthAttempts = 0; // Try credentials. List credentials = new(_settings.CredentialsOrDefault); @@ -33,7 +37,7 @@ private async Task AuthenticateAsync(SshConnection connection, CancellationToken { Credential credential = credentials[i]; - AuthResult authResult = AuthResult.Failure; + AuthResult authResult = AuthResult.Skipped; bool? methodAccepted; Name method; if (credential is PasswordCredential passwordCredential) @@ -72,6 +76,8 @@ private async Task AuthenticateAsync(SshConnection connection, CancellationToken // We didn't try the method, skip to the next credential. if (methodAccepted == false) { + rejectedMethods ??= new(); + rejectedMethods.Add(method); continue; } @@ -80,8 +86,18 @@ private async Task AuthenticateAsync(SshConnection connection, CancellationToken return; } - if (authResult == AuthResult.Failure) + if (authResult is AuthResult.Failure or AuthResult.Skipped) { + if (authResult == AuthResult.Failure) + { + failedMethods ??= new(); + failedMethods.Add(method); + } + else + { + skippedMethods ??= new(); + skippedMethods.Add(method); + } // If we didn't know if the method was accepted before, check the context which was updated by SSH_MSG_USERAUTH_FAILURE. if (methodAccepted == null) { @@ -122,7 +138,13 @@ bool TryMethod(Name credentialMethod) } } - throw new ConnectFailedException(ConnectFailedReason.AuthenticationFailed, "Authentication failed.", ConnectionInfo); + throw new ConnectFailedException( + ConnectFailedReason.AuthenticationFailed, + $"Authentication failed. {DescribeMethodListBehavior("failed", failedMethods)} {DescribeMethodListBehavior("were skipped", skippedMethods)} {DescribeMethodListBehavior("were rejected", rejectedMethods)}", ConnectionInfo); + + static string DescribeMethodListBehavior(string state, IEnumerable methods) + => methods is null ? $"No methods {state}." + : $"These methods {state}: {string.Join(", ", methods)}."; } private static Packet CreateServiceRequestMessage(SequencePool sequencePool) diff --git a/src/Tmds.Ssh/UserAuthentication.PasswordAuth.cs b/src/Tmds.Ssh/UserAuthentication.PasswordAuth.cs index f2f4c98..2d7d4db 100644 --- a/src/Tmds.Ssh/UserAuthentication.PasswordAuth.cs +++ b/src/Tmds.Ssh/UserAuthentication.PasswordAuth.cs @@ -13,21 +13,22 @@ public sealed class PasswordAuth public static async Task TryAuthenticate(PasswordCredential passwordCredential, UserAuthContext context, SshConnectionInfo connectionInfo, ILogger logger, CancellationToken ct) { string? password = passwordCredential.GetPassword(); - if (password is not null) + + if (password is null) { - context.StartAuth(AlgorithmNames.Password); + return AuthResult.Skipped; + } - logger.PasswordAuth(); + context.StartAuth(AlgorithmNames.Password); - { - using var userAuthMsg = CreatePasswordRequestMessage(context.SequencePool, context.UserName, password); - await context.SendPacketAsync(userAuthMsg.Move(), ct).ConfigureAwait(false); - } + logger.PasswordAuth(); - return await context.ReceiveAuthResultAsync(ct).ConfigureAwait(false); + { + using var userAuthMsg = CreatePasswordRequestMessage(context.SequencePool, context.UserName, password); + await context.SendPacketAsync(userAuthMsg.Move(), ct).ConfigureAwait(false); } - return AuthResult.Failure; + return await context.ReceiveAuthResultAsync(ct).ConfigureAwait(false); } private static Packet CreatePasswordRequestMessage(SequencePool sequencePool, string userName, string password) diff --git a/src/Tmds.Ssh/UserAuthentication.PublicKeyAuth.cs b/src/Tmds.Ssh/UserAuthentication.PublicKeyAuth.cs index 41ebd87..a3585f7 100644 --- a/src/Tmds.Ssh/UserAuthentication.PublicKeyAuth.cs +++ b/src/Tmds.Ssh/UserAuthentication.PublicKeyAuth.cs @@ -20,13 +20,13 @@ public static async Task TryAuthenticate(PrivateKeyCredential keyCre if (pk is null) { logger.PrivateKeyNotFound(keyCredential.Identifier); - return AuthResult.Failure; + return AuthResult.Skipped; } } catch (Exception error) { logger.PrivateKeyCanNotLoad(keyCredential.Identifier, error); - return AuthResult.Failure; + return AuthResult.Skipped; } using (pk) @@ -36,7 +36,7 @@ public static async Task TryAuthenticate(PrivateKeyCredential keyCre if (rsaKey.KeySize < context.MinimumRSAKeySize) { // TODO: log - return AuthResult.Failure; + return AuthResult.Skipped; } } @@ -69,6 +69,7 @@ public static async Task TryAuthenticate(PrivateKeyCredential keyCre if (!acceptedAlgorithm) { logger.PrivateKeyAlgorithmsNotAccepted(keyCredential.Identifier, context.PublicKeyAcceptedAlgorithms); + return AuthResult.Skipped; } }