mscdex / ssh2

SSH2 client and server modules written in pure JavaScript for node.js
MIT License
5.46k stars 667 forks source link

SSH Client fails handshake when using publickey method #1380

Open 0x7f opened 3 months ago

0x7f commented 3 months ago

I'm building a SSH server using your Node.js library and I'm building the SSH client using the x/crypto/ssh Golang library. The client aborts the SSH handshake when using the publickey authentication method.

I built an example project including server and client here: https://github.com/0x7f/ssh-authentication-bug

Even though the server offers [publickey] authentication methods when initiating the handshake with authentication method none, the client fails to connect. The client uses the publickey method and offers the key, and the server accepts the key, but the client still prints the error:

2024/03/21 09:07:40 Failed to dial: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain

I'm trying to figure out whether this is a bug in the server library or the client library. I already posted a bug in the Golang project: https://github.com/golang/go/issues/66438. Either the Node.js server library is doing something non-standard during the handshake, or the client has some issue, or I'm stupid and did something wrong in the code. The OpenSSH client works though.

Let me know when you need more information.

mscdex commented 3 months ago

Out of curiosity can you post debug output on the ssh2 side (setting debug: console.log in the server config object)?

mscdex commented 3 months ago

Also have you tried different key types to try to isolate the problem?

0x7f commented 3 months ago

Below you can find the output of the debug log. I will try other authentication types (password and different key types) and let you know.

SSH server successfully started on ssh://127.0.0.1:8022
[516174.643760916] Custom crypto binding available
[516174.643760916] Local ident: 'SSH-2.0-ssh2js1.15.0'
[516174.643760916] Remote ident: 'SSH-2.0-Go'
SSH Client connected
[516174.643760916] Outbound: Sending KEXINIT
[516174.643760916] Inbound: Handshake in progress
[516174.643760916] Handshake: (local) KEX method: curve25519-sha256@libssh.org,curve25519-sha256,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha256,diffie-hellman-group15-sha512,diffie-hellman-group16-sha512,diffie-hellman-group17-sha512,diffie-hellman-group18-sha512,kex-strict-s-v00@openssh.com
[516174.643760916] Handshake: (remote) KEX method: curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,ext-info-c,kex-strict-c-v00@openssh.com
[516174.643760916] Handshake: strict KEX mode enabled
[516174.643760916] Handshake: KEX algorithm: curve25519-sha256
[516174.643760916] Handshake: (local) Host key format: rsa-sha2-512,rsa-sha2-256,ssh-rsa
[516174.643760916] Handshake: (remote) Host key format: rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ssh-dss-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,rsa-sha2-256,rsa-sha2-512,ssh-rsa,ssh-dss,ssh-ed25519
[516174.643760916] Handshake: Host key format: rsa-sha2-256
[516174.643760916] Handshake: (local) C->S cipher: aes128-gcm@openssh.com,aes256-gcm@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,chacha20-poly1305@openssh.com
[516174.643760916] Handshake: (remote) C->S cipher: aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr
[516174.643760916] Handshake: C->S Cipher: aes128-gcm@openssh.com
[516174.643760916] Handshake: (local) S->C cipher: aes128-gcm@openssh.com,aes256-gcm@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,chacha20-poly1305@openssh.com
[516174.643760916] Handshake: (remote) S->C cipher: aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr
[516174.643760916] Handshake: S->C cipher: aes128-gcm@openssh.com
[516174.643760916] Handshake: (local) C->S MAC: hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
[516174.643760916] Handshake: (remote) C->S MAC: hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1,hmac-sha1-96
[516174.643760916] Handshake: C->S MAC: <implicit>
[516174.643760916] Handshake: (local) S->C MAC: hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
[516174.643760916] Handshake: (remote) S->C MAC: hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1,hmac-sha1-96
[516174.643760916] Handshake: S->C MAC: <implicit>
[516174.643760916] Handshake: (local) C->S compression: none,zlib@openssh.com,zlib
[516174.643760916] Handshake: (remote) C->S compression: none
[516174.643760916] Handshake: C->S compression: none
[516174.643760916] Handshake: (local) S->C compression: none,zlib@openssh.com,zlib
[516174.643760916] Handshake: (remote) S->C compression: none
[516174.643760916] Handshake: S->C compression: none
[516174.643760916] Received DH Init
[516174.643760916] Generating signature ...
[516174.643760916] Outbound: Sending KEXECDH_REPLY
[516174.643760916] Outbound: Sending NEWKEYS
[516174.643760916] Inbound: NEWKEYS
[516174.643760916] Handshake completed
[516174.643760916] Outbound: Sending EXT_INFO
[516174.643760916] Inbound: Received SERVICE_REQUEST (ssh-userauth)
[516174.643760916] Outbound: Sending SERVICE_ACCEPT (ssh-userauth)
[516174.643760916] Inbound: Received USERAUTH_REQUEST (none)
authentication none test
Auth request with method none
[516174.643760916] Outbound: Sending USERAUTH_FAILURE
[516174.643760916] Inbound: Received USERAUTH_REQUEST (publickey -- check)
authentication publickey test
User test successfully authenticated with public key
[516174.643760916] Outbound: Sending USERAUTH_PK_OK
[516174.643760916] Socket ended
Client disconnected
[516174.643760916] Socket closed
0x7f commented 3 months ago

It looks like it is working as expected when using a key of type ed25519. I created a branch in the other repo to show the client code changes: https://github.com/0x7f/ssh-authentication-bug/pull/1 The client prints this:

2024/03/21 14:55:22 Connected to SSH server

And these are the debug logs of the server:

SSH server successfully started on ssh://127.0.0.1:8022
[518853.545241708] Custom crypto binding available
[518853.545241708] Local ident: 'SSH-2.0-ssh2js1.15.0'
[518853.545241708] Remote ident: 'SSH-2.0-Go'
SSH Client connected
[518853.545241708] Outbound: Sending KEXINIT
[518853.545241708] Inbound: Handshake in progress
[518853.545241708] Handshake: (local) KEX method: curve25519-sha256@libssh.org,curve25519-sha256,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha256,diffie-hellman-group15-sha512,diffie-hellman-group16-sha512,diffie-hellman-group17-sha512,diffie-hellman-group18-sha512,kex-strict-s-v00@openssh.com
[518853.545241708] Handshake: (remote) KEX method: curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,ext-info-c,kex-strict-c-v00@openssh.com
[518853.545241708] Handshake: strict KEX mode enabled
[518853.545241708] Handshake: KEX algorithm: curve25519-sha256
[518853.545241708] Handshake: (local) Host key format: rsa-sha2-512,rsa-sha2-256,ssh-rsa
[518853.545241708] Handshake: (remote) Host key format: rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ssh-dss-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,rsa-sha2-256,rsa-sha2-512,ssh-rsa,ssh-dss,ssh-ed25519
[518853.545241708] Handshake: Host key format: rsa-sha2-256
[518853.545241708] Handshake: (local) C->S cipher: aes128-gcm@openssh.com,aes256-gcm@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,chacha20-poly1305@openssh.com
[518853.545241708] Handshake: (remote) C->S cipher: aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr
[518853.545241708] Handshake: C->S Cipher: aes128-gcm@openssh.com
[518853.545241708] Handshake: (local) S->C cipher: aes128-gcm@openssh.com,aes256-gcm@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,chacha20-poly1305@openssh.com
[518853.545241708] Handshake: (remote) S->C cipher: aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr
[518853.545241708] Handshake: S->C cipher: aes128-gcm@openssh.com
[518853.545241708] Handshake: (local) C->S MAC: hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
[518853.545241708] Handshake: (remote) C->S MAC: hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1,hmac-sha1-96
[518853.545241708] Handshake: C->S MAC: <implicit>
[518853.545241708] Handshake: (local) S->C MAC: hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
[518853.545241708] Handshake: (remote) S->C MAC: hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1,hmac-sha1-96
[518853.545241708] Handshake: S->C MAC: <implicit>
[518853.545241708] Handshake: (local) C->S compression: none,zlib@openssh.com,zlib
[518853.545241708] Handshake: (remote) C->S compression: none
[518853.545241708] Handshake: C->S compression: none
[518853.545241708] Handshake: (local) S->C compression: none,zlib@openssh.com,zlib
[518853.545241708] Handshake: (remote) S->C compression: none
[518853.545241708] Handshake: S->C compression: none
[518853.545241708] Received DH Init
[518853.545241708] Generating signature ...
[518853.545241708] Outbound: Sending KEXECDH_REPLY
[518853.545241708] Outbound: Sending NEWKEYS
[518853.545241708] Inbound: NEWKEYS
[518853.545241708] Handshake completed
[518853.545241708] Outbound: Sending EXT_INFO
[518853.545241708] Inbound: Received SERVICE_REQUEST (ssh-userauth)
[518853.545241708] Outbound: Sending SERVICE_ACCEPT (ssh-userauth)
[518853.545241708] Inbound: Received USERAUTH_REQUEST (none)
authentication none test
Auth request with method none
[518853.545241708] Outbound: Sending USERAUTH_FAILURE
[518853.545241708] Inbound: Received USERAUTH_REQUEST (publickey -- check)
authentication publickey test
User test successfully authenticated with public key
[518853.545241708] Outbound: Sending USERAUTH_PK_OK
[518853.545241708] Inbound: Received USERAUTH_REQUEST (publickey)
authentication publickey test
User test successfully authenticated with public key
[518853.545241708] Outbound: Sending USERAUTH_SUCCESS
Client authenticated!
[518853.545241708] Socket ended
Client disconnected
[518853.545241708] Socket closed
0x7f commented 3 months ago

@mscdex as far as o understand the latest comment in the other issue (https://github.com/golang/go/issues/66438#issuecomment-2016458723) the ssh2 library is doing something non-standard which is handled by OpenSSL client, but not by the go client. The go folks want to add this to their client as well. But maybe it is worth changing the ssh2 server component to be compatible with other clients still? For example, I have clients out in the wild which are compiled with the old version of the go library and are not able to connect.

0x7f commented 3 months ago

I did some more testing. I can confirm that my issue is fixed when using the proposed patch for the golang SSH client which accepts the algo returned by the ssh2 server. That's great.

But as mentioned above, I'd love to have the server compatible with old versions of the golang client as well. So I did the following local change to the PKAuthContext class:

@@ -144,11 +144,24 @@
   accept() {
     if (!this.signature) {
       this._initialResponse = true;
-      this._protocol.authPKOK(this.key.algo, this.key.data);
+      this._protocol.authPKOK(this.getResponseAlgo(), this.key.data);
     } else {
       AuthContext.prototype.accept.call(this);
     }
   }
+
+  getResponseAlgo() {
+    if (this.key.algo === 'ssh-rsa') {
+      switch (this.hashAlgo) {
+        case 'sha256':
+          return 'rsa-sha2-256';
+        case 'sha512':
+          return 'rsa-sha2-512';
+      }
+    }
+
+    return this.key.algo;
+  }
 }

 class HostbasedAuthContext extends AuthContext {

It is not beautiful and most probably the mapping belongs somewhere else (either Protocol.js or kex.js), but it works. With this change, the old golang client can connect as well. The openssh client can connect to this version of the server as well.

I'm not sure though whether this is really what the RFC requires (see this comment).

see According to RFC 4252 Section 7 the algorithm in SSH_MSG_USERAUTH_REQUEST should match that of the request, so our code was correct, but some server send the key type instead.

@mscdex can you please have a look at this? Happy to provide a PR if needed.

0x7f commented 2 months ago

@mscdex let me know if I can help in any way to get this fixed.