Xceed .NET Libraries Documentation
Keyboard Interactive Authentication

Welcome to Xceed .NET, .NET Standard and Xamarin Libraries! > Basic Concepts > SFTP Capabilities > Keyboard Interactive Authentication

SSHClient supports keyboard interactive authentication as defined by RFC 4256. Depending on the requirements of the SSH server, it can be used instead of password authentication.

Keyboard interactive is a general purpose authentication method, suitable for interactive authentications where the authentication data is entered via a keyboard or equivalent alphanumeric input device. The major goal of this method is to allow the SSH client to support a whole class of authentication mechanisms without knowing the specifics of the actual authentication mechanisms.

In practical terms, with keyboard interactive authentication, the SSH server sends text prompts to the client. These prompts must be given a text answer to. The client sends the responses back to the server. If accepted, more prompts can be sent to the client for response or the authentication can be declared successful or failed.

The text prompts are arbitrary strings. There are no standard or predefined texts. The server decides the content of the prompts and what the required response is. As such, the SSHClient class cannot parse or process the prompts it receives as part of keyboard interactive authentication. Your application must process the prompts and supply responses programmatically or display the prompts to the end-user for them to type the responses.

To authenticate with keyboard interactive, call Authenticate with a userName and a KeyBoardInteractiveAuthenticationHandler delegate.

ssh.Authenticate( "user name", myHandler );
ssh.Authenticate("user name", myHandler)

The delegate KeyBoardInteractiveAuthenticationHandler is a callback method that will be invoked as part of keyboard interactive authentication. It is defined as follows:

public delegate void KeyBoardInteractiveAuthenticationHandler(
  string userName,
  string name,
  string instruction,
  string languageTag,
  KeyboardInteractiveRequest[] requests );
Public Delegate Sub KeyBoardInteractiveAuthenticationHandler(ByVal userName As String, ByVal name As String, ByVal instruction As String, ByVal languageTag As String, ByVal requests() As KeyboardInteractiveRequest)

Where:

userName is the user name that was specified in the call to the Authenticate method.

name is a server-supplied string that may indicate a logical name for the series of requests. It can be an empty string, but it will not be null.

instruction is a server-supplied string that may indicate instructions on how to respond to the series of requests. It can be an empty string, but it will not be null.

languageTag is a string that specifies the language of the messages. In most cases, this parameter will be an empty string, and the language will be English.

requests is an array of KeyboardInteractiveRequest objects that specify the information prompts and their responses. The array will not be null. Each element in the array represents a prompt that must be answered. The response for each element is prefilled with an empty string. It is possible that the array will be empty, but it will not be null.

KeyboardInteractiveRequest is a simple class that holds the following properties:

Prompt is the server-supplied string that specifies what information is required. For example, it could be something like "Password:". There are no pre-defined prompt strings. The server can supply any text it wants here.

Echo is a boolean value that specifies whether the response to the prompt should be echoed to the screen. In general, this value will be false when the prompt refers to sensitive information like passwords and true otherwise.

Response is the string that will contain the response to the prompt. Your handler will set the this property's value. The string will then be sent back to the server for authentication. The response can be set to an empty string. If the property is set to null, an empty string will be sent back to the server.

Each request in the requests array represents a prompt. If the prompts are presented to an end-user, each prompt should be displayed to the user one by one and in order. The requests array will typically contain one prompt but it might contain more. The KeyBoardInteractiveAuthenticationHandler might be called again with more prompts. There is no predefined limit on the number of prompts that may be asked.

The KeyBoardInteractiveAuthenticationHandler will be invoked by the component on the same thread that Authenticate() was called on. Authenticate() will therefore block while it waits for the KeyBoardInteractiveAuthenticationHandler to return. The component does not impose any timeout on how long control can stay in the handler. However, be aware that some SSH servers enforce a limit on how long authentication takes. For example, the default limit on the OpenSSH server is 120 seconds.

How to implement KeyBoardInteractiveAuthenticationHandler

If you are certain of the contents and formatting of the prompts you will receive from the server, you may implement a KeyBoardInteractiveAuthenticationHandler method that processes and answers the prompts automatically.

Consider this example for OpenSSH servers that use the ChallengeResponseAuthentication option:

public static void KeyBoardInteractiveAuthenticationHandlerLinuxPAM( string userName, string name, string instruction, string languageTag, KeyboardInteractiveRequest[] requests )
{
  // If we have a request
  if( requests.Length > 0 )
  {
    // If the first request is the string 'Password: '
    if( StringComparer.OrdinalIgnoreCase.Compare( requests[ 0 ].Prompt, "Password: " ) == 0 )
    {
      // Supply our password as the response
      requests[ 0 ].Response = "<your password>";
    }
  }
}
Public Shared Sub KeyBoardInteractiveAuthenticationHandlerLinuxPAM(ByVal userName As String, ByVal name As String, ByVal instruction As String, ByVal languageTag As String, ByVal requests() As KeyboardInteractiveRequest)
  ' If we have a request
  If requests.Length > 0 Then
    ' If the first request is the string 'Password: '
    If StringComparer.OrdinalIgnoreCase.Compare(requests(0).Prompt, "Password: ") = 0 Then
      ' Supply our password as the response
      requests(0).Response = "<your password>"
    End If
  End If
End Sub

Another way to approach this authentication is to display the prompts to the console and accept input for the answers.

public static void KeyBoardInteractiveAuthenticationHandlerConsole( string userName, string name, string instruction, string languageTag, KeyboardInteractiveRequest[] requests )
{
  // If the name is non-empty
  if( !String.IsNullOrEmpty( name ) )
  {
    // Display it
    Console.WriteLine( name );
  }

  // If the instruction is non-empty
  if( !String.IsNullOrEmpty( instruction ) )
  {
    // Display it
    Console.WriteLine( instruction );
  }

  // If we have a request
  if( requests.Length > 0 )
  {
    // Go through each request in order
    foreach( KeyboardInteractiveRequest request in requests )
    {
      // Display the prompt
      Console.Write( request.Prompt );

      // If we can display the response as it is being typed
      if( request.Echo )
      {
        // Read the next line of text from the console
        request.Response = Console.ReadLine();
      }
      // We can't display the response
      else
      {
        StringBuilder response = new StringBuilder();

        // Read a key without displaying it
        ConsoleKeyInfo keyInfo = Console.ReadKey( true );

        // Until <Enter> is pressed
        while( keyInfo.Key != ConsoleKey.Enter )
        {
          // Add it to the response
          response.Append( keyInfo.KeyChar );

          // Read a key without displaying it
          keyInfo = Console.ReadKey( true );
        }

        // Store the response string in the request
        request.Response = response.ToString();
      }
    }
  }
}
Public Shared Sub KeyBoardInteractiveAuthenticationHandlerConsole(ByVal userName As String, ByVal name As String, ByVal instruction As String, ByVal languageTag As String, ByVal requests() As KeyboardInteractiveRequest)
  ' If the name is non-empty
  If (Not String.IsNullOrEmpty(name)) Then
    ' Display it
    Console.WriteLine(name)
  End If

  ' If the instruction is non-empty
  If (Not String.IsNullOrEmpty(instruction)) Then
    ' Display it
    Console.WriteLine(instruction)
  End If

  ' If we have a request
  If requests.Length > 0 Then
    ' Go through each request in order
    For Each request As KeyboardInteractiveRequest In requests
      ' Display the prompt
      Console.Write(request.Prompt)

      ' If we can display the response as it is being typed
      If request.Echo Then
        ' Read the next line of text from the console
        request.Response = Console.ReadLine()
        ' We can't display the response
      Else
        Dim response As New StringBuilder()

        ' Read a key without displaying it
        Dim keyInfo As ConsoleKeyInfo = Console.ReadKey(True)

        ' Until <Enter> is pressed
        Do While keyInfo.Key <> ConsoleKey.Enter
          ' Add it to the response
          response.Append(keyInfo.KeyChar)

          ' Read a key without displaying it
          keyInfo = Console.ReadKey(True)
        Loop

        ' Store the response string in the request
        request.Response = response.ToString()
      End If
    Next request
  End If
End Sub

Here's an example that puts it all together, with the exceptions that Authenticate can throw when called for the keyboard interactive method.

static void Example()
{
  string host = "<host>";
  string username = "<username>";

  using( SSHClient ssh = new SSHClient() )
  {
    ssh.Connect( host );

    KeyBoardInteractiveAuthenticationHandler consoleHandler = new KeyBoardInteractiveAuthenticationHandler( KeyBoardInteractiveAuthenticationHandlerConsole );

    bool retry = false;

    do
    {
      try
      {
        // Authenticate with the keyboard interactive method
        ssh.Authenticate( username, consoleHandler );
      }
      // Authentication was successful but more authentication is required by the server
      catch( SSHAuthenticationPartialSuccessException e )
      {
        // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
        e.AuthenticationsThatCanContinue.ToString();

        throw;
      }
      // The server rejected one of the keyboard response the user typed. Authentication has failed
      catch( SSHIncorrectResponseException e )
      {
        SSHAuthenticationFailedException authenticationFailedException = e.InnerException as SSHAuthenticationFailedException;

        if( authenticationFailedException != null )
        {
          // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
          authenticationFailedException.AuthenticationsThatCanContinue.ToString();
        }

        /* TODO: Decide if it is wise to retry the keyboard interactive method, how many times, etc */
        retry = true;

        if( !retry )
        {
          throw;
        }
      }
      catch( SSHUnsupportedAuthenticationMethodException e )
      {
        // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
        e.AuthenticationsThatCanContinue.ToString();

        throw;
      }
      catch( SSHAuthenticationFailedException e )
      {
        // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
        e.AuthenticationsThatCanContinue.ToString();

        throw;
      }
    }
    while( retry );

    /* TODO: Perform SSH/SFtp operations */
    using( SFtpSession sftp = new SFtpSession( ssh ) )
    {

    }
  }
}
Private Shared Sub Example()
  Dim host As String = "<host>"
  Dim username As String = "<username>"

  Using ssh As New SSHClient()
    ssh.Connect(host)

    Dim consoleHandler As New KeyBoardInteractiveAuthenticationHandler(AddressOf KeyBoardInteractiveAuthenticationHandlerConsole)

    Dim retry As Boolean = False

    Do
      Try
        ' Authenticate with the keyboard interactive method
        ssh.Authenticate(username, consoleHandler)
        ' Authentication was successful but more authentication is required by the server
      Catch e As SSHAuthenticationPartialSuccessException
        ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
        e.AuthenticationsThatCanContinue.ToString()

        Throw
        ' The server rejected one of the keyboard response the user typed. Authentication has failed
      Catch e As SSHIncorrectResponseException
        Dim authenticationFailedException As SSHAuthenticationFailedException = TryCast(e.InnerException, SSHAuthenticationFailedException)

        If authenticationFailedException IsNot Nothing Then
          ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
          authenticationFailedException.AuthenticationsThatCanContinue.ToString()
        End If

        ' TODO: Decide if it is wise to retry the keyboard interactive method, how many times, etc 
        retry = True

        If (Not retry) Then
          Throw
        End If
      Catch e As SSHUnsupportedAuthenticationMethodException
        ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
        e.AuthenticationsThatCanContinue.ToString()

        Throw
      Catch e As SSHAuthenticationFailedException
        ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
        e.AuthenticationsThatCanContinue.ToString()

        Throw
      End Try
    Loop While retry

    ' TODO: Perform SSH/SFtp operations 
    Using sftp As New SFtpSession(ssh)

    End Using
  End Using
End Sub

Some servers require multiple authentications be used to log in. Here is an example that chains different authentications one after the other.

static void ChainingExample()
{
  string host = "<host>";
  string username = "<username>";
  string password = "<password>";

  using( SSHClient ssh = new SSHClient() )
  {
    ssh.Connect( host );

    bool needsMoreAuthentication = false;

    try
    {
      // Authenticate with the username/password method
      ssh.Authenticate( username, password );
    }
    // Authentication was successful but more authentication is required by the server
    catch( SSHAuthenticationPartialSuccessException e )
    {
      /* We have successfully authenticated with username/password, but the server needs us
       * to use additional authentication methods */
      needsMoreAuthentication = true;

      // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
      e.AuthenticationsThatCanContinue.ToString();
    }
    catch( SSHUnsupportedAuthenticationMethodException e )
    {
      /* The server doesn't support this authentication. We will use additional authentication
        * methods we support */
      needsMoreAuthentication = true;

      // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
      e.AuthenticationsThatCanContinue.ToString();
    }
    catch( SSHAuthenticationFailedException e )
    {
      // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
      e.AuthenticationsThatCanContinue.ToString();

      throw;
    }

    // If we need to perform more authentication
    if( needsMoreAuthentication )
    {
      /* We will use the other authentication method we have */

      KeyBoardInteractiveAuthenticationHandler consoleHandler = new KeyBoardInteractiveAuthenticationHandler( KeyBoardInteractiveAuthenticationHandlerConsole );

      bool retry = false;

      do
      {
        try
        {
          // Authenticate with the keyboard interactive method
          ssh.Authenticate( username, consoleHandler );

          // If we reach here, we have used all our authentication methods and they have been successful
          needsMoreAuthentication = false;
        }
        // Authentication was successful but more authentication is required by the server
        catch( SSHAuthenticationPartialSuccessException e )
        {
          /* At this point, we are out of authentication methods... */

          throw new Exception( "Can't authenticate. Used up all authentication methods we support.", e );
        }
        // The server rejected one of the keyboard response the user typed. Authentication has failed
        catch( SSHIncorrectResponseException e )
        {
          SSHAuthenticationFailedException authenticationFailedException = e.InnerException as SSHAuthenticationFailedException;

          if( authenticationFailedException != null )
          {
            // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
            authenticationFailedException.AuthenticationsThatCanContinue.ToString();
          }

          /* TODO: Decide if it is wise to retry the keyboard interactive method, how many times, etc */
          retry = true;

          if( !retry )
          {
            throw;
          }
        }
        catch( SSHUnsupportedAuthenticationMethodException e )
        {
          // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
          e.AuthenticationsThatCanContinue.ToString();

          /* At this point, we are out of authentication methods... */

          throw new Exception( "Can't authenticate. Used up all authentication methods we support.", e );
        }
        catch( SSHAuthenticationFailedException e )
        {
          // The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
          e.AuthenticationsThatCanContinue.ToString();

          throw;
        }
      }
      while( retry );
    }

    /* TODO: Perform SSH/SFtp operations */
    using( SFtpSession sftp = new SFtpSession( ssh ) )
    {

    }
  }
}
Private Shared Sub ChainingExample()
  Dim host As String = "<host>"
  Dim username As String = "<username>"
  Dim password As String = "<password>"

  Using ssh As New SSHClient()
    ssh.Connect(host)

    Dim needsMoreAuthentication As Boolean = False

    Try
      ' Authenticate with the username/password method
      ssh.Authenticate(username, password)
      ' Authentication was successful but more authentication is required by the server
    Catch e As SSHAuthenticationPartialSuccessException
      '           We have successfully authenticated with username/password, but the server needs us
      '           * to use additional authentication methods 
      needsMoreAuthentication = True

      ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
      e.AuthenticationsThatCanContinue.ToString()
    Catch e As SSHUnsupportedAuthenticationMethodException
      '           The server doesn't support this authentication. We will use additional authentication
      '            * methods we support 
      needsMoreAuthentication = True

      ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
      e.AuthenticationsThatCanContinue.ToString()
    Catch e As SSHAuthenticationFailedException
      ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
      e.AuthenticationsThatCanContinue.ToString()

      Throw
    End Try

    ' If we need to perform more authentication
    If needsMoreAuthentication Then
      ' We will use the other authentication method we have 

      Dim consoleHandler As New KeyBoardInteractiveAuthenticationHandler(AddressOf KeyBoardInteractiveAuthenticationHandlerConsole)

      Dim retry As Boolean = False

      Do
        Try
          ' Authenticate with the keyboard interactive method
          ssh.Authenticate(username, consoleHandler)

          ' If we reach here, we have used all our authentication methods and they have been successful
          needsMoreAuthentication = False
          ' Authentication was successful but more authentication is required by the server
        Catch e As SSHAuthenticationPartialSuccessException
          ' At this point, we are out of authentication methods... 

          Throw New Exception("Can't authenticate. Used up all authentication methods we support.", e)
          ' The server rejected one of the keyboard response the user typed. Authentication has failed
        Catch e As SSHIncorrectResponseException
          Dim authenticationFailedException As SSHAuthenticationFailedException = TryCast(e.InnerException, SSHAuthenticationFailedException)

          If authenticationFailedException IsNot Nothing Then
            ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
            authenticationFailedException.AuthenticationsThatCanContinue.ToString()
          End If

          ' TODO: Decide if it is wise to retry the keyboard interactive method, how many times, etc 
          retry = True

          If (Not retry) Then
            Throw
          End If
        Catch e As SSHUnsupportedAuthenticationMethodException
          ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
          e.AuthenticationsThatCanContinue.ToString()

          ' At this point, we are out of authentication methods... 

          Throw New Exception("Can't authenticate. Used up all authentication methods we support.", e)
        Catch e As SSHAuthenticationFailedException
          ' The 'AuthenticationsThatCanContinue' property specifies the authentications methods that can be tried
          e.AuthenticationsThatCanContinue.ToString()

          Throw
        End Try
      Loop While retry
    End If

    ' TODO: Perform SSH/SFtp operations 
    Using sftp As New SFtpSession(ssh)

    End Using
  End Using
End Sub
See Also

General information