Cheshire Cat
Cheshire Cat

Reputation: 1961

Use SSH.NET to connect using SSH Key and Two-factor Authentication

I'm trying to use SSH.NET library (v2020.0.2 - latest) on C# Desktop application, to connect to a remote Linux (Debian) server.

The SSH server is configured as follows:

This is the code I'm using to establish the connection:

try
{
    PrivateKeyFile privateKey = new PrivateKeyFile(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".ssh", "id_rsa"), "passphrase");
    SshClient sshClient = new SshClient("192.168.0.1", 22, "user", privateKey);
    sshClient.Connect(); // --> Exception
    SshCommand command = sshClient.CreateCommand("ls -la");
    command.Execute();
    string answer = command.Result;
}
catch (Exception)
{
    throw;
}

But it's giving me the following Exception:

Renci.SshNet.Common.SshAuthenticationException: 'No suitable authentication method found to complete authentication (keyboard-interactive).'

According to SSH.NET README.md on GitHub it should be compatible with 2FA but I'm not sure where is the real issue.

SSH.Net Features list

Also, how am I supposed to handle the Verification Code prompt?

Note: If I connect from the Windows 10 Command Prompt using ssh command from the same PC, it works. It first asks for the SSH Key passphrase and then it asks for the Verification Code.

2022-09-02 Update

Updated code using SSH.NET

try
{
    PrivateKeyAuthenticationMethod privateKeyAuthentication = new PrivateKeyAuthenticationMethod("user", 
        new PrivateKeyFile(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".ssh", "id_ecdsa"), "passphrase"));
    
    KeyboardInteractiveAuthenticationMethod keyboardAuthentication = new KeyboardInteractiveAuthenticationMethod("user");
    keyboardAuthentication.AuthenticationPrompt += KeyboardAuthentication_AuthenticationPrompt;
    
    ConnectionInfo connectionInfo = new ConnectionInfo("192.168.0.1", 22, "user", privateKeyAuthentication, keyboardAuthentication);
    SshClient sshClient = new SshClient(connectionInfo);
    sshClient.Connect();
    SshCommand command = sshClient.CreateCommand("ls -la");
    command.Execute();
    string answer = command.Result;
}
catch (Exception)
{
    throw;
}

private async void KeyboardAuthentication_AuthenticationPrompt(object sender, Renci.SshNet.Common.AuthenticationPromptEventArgs e)
{
    foreach (AuthenticationPrompt prompt in e.Prompts)
    {
        if (prompt.Request.StartsWith("Password"))
            prompt.Response = "password"; // Hard coded here just for the sample
        else if (prompt.Request.StartsWith("Verification"))
            prompt.Response = "..."; // Insert Verification code at runtime using here a Debugger breakpoint
    }
}

Client connection ssh verbose log

C:\Users\my.user>ssh -vvv [email protected] -i C:\Users\my.user\.ssh\id_ecdsa.pem
OpenSSH_for_Windows_8.1p1, LibreSSL 3.0.2
debug3: Failed to open file:C:/Users/my.user/.ssh/config error:2
debug3: Failed to open file:C:/ProgramData/ssh/ssh_config error:2
debug2: resolve_canonicalize: hostname 192.168.133.128 is address
debug2: ssh_connect_direct
debug1: Connecting to 192.168.133.128 [192.168.133.128] port 22.
debug1: Connection established.
debug3: Failed to open file:C:/Users/my.user/.ssh/id_ecdsa.pem.pub error:2
debug1: identity file C:\\Users\\my.user\\.ssh\\id_ecdsa.pem type -1
debug3: Failed to open file:C:/Users/my.user/.ssh/id_ecdsa.pem-cert error:2
debug3: Failed to open file:C:/Users/my.user/.ssh/id_ecdsa.pem-cert.pub error:2
debug1: identity file C:\\Users\\my.user\\.ssh\\id_ecdsa.pem-cert type -1
debug1: Local version string SSH-2.0-OpenSSH_for_Windows_8.1
debug1: Remote protocol version 2.0, remote software version OpenSSH_8.4p1 Debian-5+deb11u1
debug1: match: OpenSSH_8.4p1 Debian-5+deb11u1 pat OpenSSH* compat 0x04000000
debug2: fd 3 setting O_NONBLOCK
debug1: Authenticating to 192.168.133.128:22 as 'admin'
debug3: hostkeys_foreach: reading file "C:\\Users\\my.user/.ssh/known_hosts"
debug3: record_hostkey: found key type ECDSA in file C:\\Users\\my.user/.ssh/known_hosts:1
debug3: load_hostkeys: loaded 1 keys from 192.168.133.128
debug3: Failed to open file:C:/Users/my.user/.ssh/known_hosts2 error:2
debug3: Failed to open file:C:/ProgramData/ssh/ssh_known_hosts error:2
debug3: Failed to open file:C:/ProgramData/ssh/ssh_known_hosts2 error:2
debug3: order_hostkeyalgs: prefer hostkeyalgs: [email protected],[email protected],[email protected],ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521
debug3: send packet: type 20
debug1: SSH2_MSG_KEXINIT sent
debug3: receive packet: type 20
debug1: SSH2_MSG_KEXINIT received
debug2: local client KEXINIT proposal
debug2: KEX algorithms: curve25519-sha256,[email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,ext-info-c
debug2: host key algorithms: [email protected],[email protected],[email protected],ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,[email protected],[email protected],[email protected],[email protected],ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa
debug2: ciphers ctos: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected]
debug2: ciphers stoc: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected]
debug2: MACs ctos: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: MACs stoc: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: compression ctos: none,[email protected],zlib
debug2: compression stoc: none,[email protected],zlib
debug2: languages ctos:
debug2: languages stoc:
debug2: first_kex_follows 0
debug2: reserved 0
debug2: peer server KEXINIT proposal
debug2: KEX algorithms: curve25519-sha256,[email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256
debug2: host key algorithms: rsa-sha2-512,rsa-sha2-256,ssh-rsa,ecdsa-sha2-nistp256,ssh-ed25519
debug2: ciphers ctos: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected]
debug2: ciphers stoc: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected]
debug2: MACs ctos: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: MACs stoc: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: compression ctos: none,[email protected]
debug2: compression stoc: none,[email protected]
debug2: languages ctos:
debug2: languages stoc:
debug2: first_kex_follows 0
debug2: reserved 0
debug1: kex: algorithm: curve25519-sha256
debug1: kex: host key algorithm: ecdsa-sha2-nistp256
debug1: kex: server->client cipher: [email protected] MAC: <implicit> compression: none
debug1: kex: client->server cipher: [email protected] MAC: <implicit> compression: none
debug3: send packet: type 30
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug3: receive packet: type 31
debug1: Server host key: ecdsa-sha2-nistp256 SHA256:--<OMITTED>--
debug3: hostkeys_foreach: reading file "C:\\Users\\my.user/.ssh/known_hosts"
debug3: record_hostkey: found key type ECDSA in file C:\\Users\\my.user/.ssh/known_hosts:1
debug3: load_hostkeys: loaded 1 keys from 192.168.133.128
debug3: Failed to open file:C:/Users/my.user/.ssh/known_hosts2 error:2
debug3: Failed to open file:C:/ProgramData/ssh/ssh_known_hosts error:2
debug3: Failed to open file:C:/ProgramData/ssh/ssh_known_hosts2 error:2
debug1: Host '192.168.133.128' is known and matches the ECDSA host key.
debug1: Found key in C:\\Users\\my.user/.ssh/known_hosts:1
debug3: send packet: type 21
debug2: set_newkeys: mode 1
debug1: rekey out after 134217728 blocks
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug3: receive packet: type 21
debug1: SSH2_MSG_NEWKEYS received
debug2: set_newkeys: mode 0
debug1: rekey in after 134217728 blocks
debug3: unable to connect to pipe \\\\.\\pipe\\openssh-ssh-agent, error: 2
debug1: pubkey_prepare: ssh_get_authentication_socket: No such file or directory
debug1: Will attempt key: C:\\Users\\my.user\\.ssh\\id_ecdsa.pem  explicit
debug2: pubkey_prepare: done
debug3: send packet: type 5
debug3: receive packet: type 7
debug1: SSH2_MSG_EXT_INFO received
debug1: kex_input_ext_info: server-sig-algs=<ssh-ed25519,[email protected],ssh-rsa,rsa-sha2-256,rsa-sha2-512,ssh-dss,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,[email protected],[email protected]>
debug3: receive packet: type 6
debug2: service_accept: ssh-userauth
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug3: send packet: type 50
debug3: receive packet: type 51
debug1: Authentications that can continue: publickey
debug3: start over, passed a different list publickey
debug3: preferred publickey,keyboard-interactive,password
debug3: authmethod_lookup publickey
debug3: remaining preferred: keyboard-interactive,password
debug3: authmethod_is_enabled publickey
debug1: Next authentication method: publickey
debug1: Trying private key: C:\\Users\\my.user\\.ssh\\id_ecdsa.pem
debug3: failed to open file:C:/dev/tty error:3
debug1: read_passphrase: can't open /dev/tty: No such file or directory
Enter passphrase for key 'C:\Users\my.user\.ssh\id_ecdsa.pem':
debug3: sign_and_send_pubkey: ECDSA SHA256:--<OMITTED>--
debug3: sign_and_send_pubkey: signing using ecdsa-sha2-nistp521
debug3: send packet: type 50
debug2: we sent a publickey packet, wait for reply
debug3: receive packet: type 51
Authenticated with partial success.
debug1: Authentications that can continue: keyboard-interactive
debug3: start over, passed a different list keyboard-interactive
debug3: preferred publickey,keyboard-interactive,password
debug3: authmethod_lookup keyboard-interactive
debug3: remaining preferred: password
debug3: authmethod_is_enabled keyboard-interactive
debug1: Next authentication method: keyboard-interactive
debug2: userauth_kbdint
debug3: send packet: type 50
debug2: we sent a keyboard-interactive packet, wait for reply
debug3: receive packet: type 60
debug2: input_userauth_info_req
debug2: input_userauth_info_req: num_prompts 1
debug3: failed to open file:C:/dev/tty error:3
debug1: read_passphrase: can't open /dev/tty: No such file or directory
Verification code:
debug3: send packet: type 61
debug3: receive packet: type 60
debug2: input_userauth_info_req
debug2: input_userauth_info_req: num_prompts 0
debug3: send packet: type 61
debug3: receive packet: type 52
debug1: Authentication succeeded (keyboard-interactive).
Authenticated to 192.168.133.128 ([192.168.133.128]:22).
debug1: channel 0: new [client-session]
debug3: ssh_session2_open: channel_new: 0
debug2: channel 0: send open
debug3: send packet: type 90
debug1: Requesting [email protected]
debug3: send packet: type 80
debug1: Entering interactive session.
debug1: pledge: network
debug1: ENABLE_VIRTUAL_TERMINAL_INPUT is supported. Reading the VTSequence from console
debug3: This windows OS supports conpty
debug1: ENABLE_VIRTUAL_TERMINAL_PROCESSING is supported. Console supports the ansi parsing
debug3: Successfully set console output code page from:65001 to 65001
debug3: Successfully set console input code page from:850 to 65001
debug3: receive packet: type 80
debug1: client_input_global_request: rtype [email protected] want_reply 0
debug3: receive packet: type 4
debug1: Remote: /home/admin/.ssh/authorized_keys:3: key options: agent-forwarding port-forwarding pty user-rc x11-forwarding
debug3: receive packet: type 91
debug2: channel_input_open_confirmation: channel 0: callback start
debug2: fd 3 setting TCP_NODELAY
debug2: client_session2_setup: id 0
debug2: channel 0: request pty-req confirm 1
debug3: send packet: type 98
debug2: channel 0: request shell confirm 1
debug3: send packet: type 98
debug2: channel_input_open_confirmation: channel 0: callback done
debug2: channel 0: open confirm rwindow 0 rmax 32768
debug3: receive packet: type 99
debug2: channel_input_status_confirm: type 99 id 0
debug2: PTY allocation request accepted on channel 0
debug2: channel 0: rcvd adjust 2097152
debug3: receive packet: type 99
debug2: channel_input_status_confirm: type 99 id 0
debug2: shell request accepted on channel 0
The programs included are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Last login: Thu Sep  1 15:37:31 2022 from 192.168.133.1
┌─[admin@server]─[~]
└──╼ $

2022-09-13 Update

Here I'm posting the log files from sshd in verbose debug mode (-ddd option) when connecting from cmd.exe (OK) and from my application using SSH.NET (NOT WORKING), both with same user credentials.

NOTE: I used PasteBin since the logs where too long for pasting here inside the question.

Some differences I noticed:

SSH.NET

debug1: Remote protocol version 2.0, remote software version Renci.SshNet.SshClient.0.0.1
---- What does it means 'no match'? ----
debug1: no match: Renci.SshNet.SshClient.0.0.1

...

debug3: PAM: sshpam_query entering
debug3: ssh_msg_recv entering
---- There is no do_pam_account here ----
debug3: PAM: Authentication failure
PAM: Authentication failure for admin from 192.168.133.1

cmd.exe

debug1: Remote protocol version 2.0, remote software version OpenSSH_for_Windows_8.1
debug1: match: OpenSSH_for_Windows_8.1 pat OpenSSH compat 0x04000000

...

debug3: PAM: sshpam_query entering
debug3: ssh_msg_recv entering
debug1: do_pam_account: called
debug2: do_pam_account: auth information in SSH_AUTH_INFO_0

Upvotes: 1

Views: 3959

Answers (1)

Martin Prikryl
Martin Prikryl

Reputation: 202088

From ChallengeResponseAuthentication, I assume the libpam-google-authenticator is implemented by keyboard-interactive authentication. For that, see SSH.NET - No suitable authentication method found.

To combine it with private key factor, do something like this:

var privateKeyAuth = new PrivateKeyAuthenticationMethod(username, privateKey);

var keybAuth = new KeyboardInteractiveAuthenticationMethod(username);
keybAuth.AuthenticationPrompt +=
    delegate (object sender, AuthenticationPromptEventArgs e)
    {
        // Your implementation of the prompt. Something like this:
        if ((e.Prompts.Count() != 1) ||
            (e.Prompts.First().Request != "Verification code:"))
        {
            throw new InvalidOperationException("Unexpected auth prompt");
        }
        e.Prompts.First().Response = "verification code";
    };

var connectionInfo = new ConnectionInfo(host, username, privateKeyAuth, keybAuth);

var sshClient = new SshClient(connectionInfo);

Upvotes: 3

Related Questions