Page MenuHomePhabricator

Server incorrectly assumes rsa-sha2-256 when client meant standard ssh-rsa
Closed, ResolvedPublic


The libssh server incorrectly assumes rsa-sha2-256 when client meant standard ssh-rsa.
I found this bug while adapting PKD so it'd automatically support all ciphers supported with ssh -Q.

The bug can be trigged by starting a libssh server with minimum parameters:

examples/samplesshd-kbdint -k tests/home/bob/.ssh/id_rsa -p 2222 -vvv

The client can be started with this:

/home/aris/git/openssh-portable/ssh -o HostKeyAlgorithms=ssh-rsa,rsa-sha2-256 -vvv -p 2222 localhost id

It requires a recent OpenSSH. It doesn't work if ssh-rsa is omitted or after rsa-sha2-256. Both parameters need to be sent by the client.

We get this on the server side:

Starting program: /home/aris/git/libssh/build/examples/samplesshd-kbdint -k ../tests/home/bob/.ssh/id_rsa -p 2222 -vvv
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/".
Started sample libssh sshd on port 2222
You can login as the user libssh with the password libssh
[2019/10/27 22:40:45.183617, 3] ssh_socket_pollcallback:  Received POLLOUT in connecting state
[2019/10/27 22:40:45.183701, 3] ssh_socket_unbuffered_write:  Enabling POLLOUT for socket
[2019/10/27 22:40:45.183887, 3] callback_receive_banner:  Received banner: SSH-2.0-OpenSSH_8.1
[2019/10/27 22:40:45.183913, 2] ssh_server_connection_callback:  SSH client banner: SSH-2.0-OpenSSH_8.1
[2019/10/27 22:40:45.183926, 2] ssh_analyze_banner:  Analyzing banner: SSH-2.0-OpenSSH_8.1
[2019/10/27 22:40:45.183943, 2] ssh_analyze_banner:  We are talking to an OpenSSH client version: 8.1 (80100)
[2019/10/27 22:40:45.183959, 3] ssh_log_hexdump:  session cookie (16 bytes):
[2019/10/27 22:40:45.183991, 3] ssh_log_hexdump:    00000000  24 59 44 34 ad a3 9f ee  a7 c5 64 fb e3 5d b9 a8   $YD4......d..]..
[2019/10/27 22:40:45.184051, 3] ssh_socket_unbuffered_write:  Enabling POLLOUT for socket
[2019/10/27 22:40:45.184074, 3] packet_send2:  packet: wrote [type=20, len=852, padding_size=7, comp=844, payload=844]
[2019/10/27 22:40:45.184092, 3] ssh_send_kex:  SSH_MSG_KEXINIT sent
[2019/10/27 22:40:45.184138, 3] ssh_packet_socket_callback:  packet: read type 20 [len=1052,padding=6,comp=1045,payload=1045]
[2019/10/27 22:40:45.184158, 3] ssh_packet_process:  Dispatching handler for packet type 20
[2019/10/27 22:40:45.184204, 3] ssh_packet_kexinit:  The client supports extension negotiation. Enabled signature algorithms: SHA256
[2019/10/27 22:40:45.184224, 3] ssh_log_hexdump:  session cookie (16 bytes):
[2019/10/27 22:40:45.184247, 3] ssh_log_hexdump:    00000000  83 61 31 bb 47 d6 bc 0d  c2 82 58 9c 40 9c e9 39   .a1.G.....X.@..9
[2019/10/27 22:40:45.184290, 2] ssh_kex_select_methods:  Negotiated curve25519-sha256,ssh-rsa,aes128-ctr,aes128-ctr,,,none,none,,
[2019/10/27 22:40:45.184310, 3] crypt_set_algorithms_server:  Set output algorithm aes128-ctr
[2019/10/27 22:40:45.184325, 3] crypt_set_algorithms_server:  Set HMAC output algorithm to
[2019/10/27 22:40:45.184339, 3] crypt_set_algorithms_server:  Set input algorithm aes128-ctr
[2019/10/27 22:40:45.184357, 3] crypt_set_algorithms_server:  Set HMAC input algorithm to
[2019/10/27 22:40:45.187655, 3] ssh_packet_socket_callback:  packet: read type 30 [len=44,padding=6,comp=37,payload=37]
[2019/10/27 22:40:45.187668, 3] ssh_packet_process:  Dispatching handler for packet type 30
[2019/10/27 22:40:45.187673, 3] ssh_packet_kexdh_init:  Received SSH_MSG_KEXDH_INIT
[2019/10/27 22:40:45.187677, 3] ssh_packet_kexdh_init:  Calling next KEXDH handler
[2019/10/27 22:40:45.187868, 3] ssh_log_hexdump:  Session server cookie (16 bytes):
[2019/10/27 22:40:45.187878, 3] ssh_log_hexdump:    00000000  24 59 44 34 ad a3 9f ee  a7 c5 64 fb e3 5d b9 a8   $YD4......d..]..
[2019/10/27 22:40:45.187883, 3] ssh_log_hexdump:  Session client cookie (16 bytes):
[2019/10/27 22:40:45.187889, 3] ssh_log_hexdump:    00000000  83 61 31 bb 47 d6 bc 0d  c2 82 58 9c 40 9c e9 39   .a1.G.....X.@..9
Shared secret key value: 526C43A34E32145ACD96837AA918418B4F8B53CC785D5F59208B3E3833AF8F7D
17 bits, 3 bytes, 0 padding
2048 bits, 256 bytes, 1 padding
Importing a 24 bits, 3 bytes object ...
Importing a 2056 bits, 257 bytes object ...
[2019/10/27 22:40:45.187919, 3] ssh_log_hexdump:  e (3 bytes):
[2019/10/27 22:40:45.187927, 3] ssh_log_hexdump:    00000000  01 00 01                                           ...
[2019/10/27 22:40:45.187933, 3] ssh_log_hexdump:  n (257 bytes):
Breakpoint 2, ssh_srv_pki_do_sign_sessionid (session=0x555555758fe0, privkey=0x55555575a6b0) at /home/aris/git/libssh/src/pki.c:2427
2427	{
(gdb) print privkey
$1 = (const ssh_key) 0x55555575a6b0
(gdb) print *privkey
$2 = {type = SSH_KEYTYPE_RSA, flags = 3, type_c = 0x7ffff7bc0619 "ssh-rsa", ecdsa_nid = 0, dsa = 0x0, rsa = 0x55555575a710, ecdsa = 0x0, 
  ed25519_pubkey = 0x0, ed25519_privkey = 0x0, cert = 0x0, cert_type = SSH_KEYTYPE_UNKNOWN}
(gdb) next
2428	    struct ssh_crypto_struct *crypto = NULL;
2430	    ssh_signature sig = NULL;
2431	    ssh_string sig_blob = NULL;
2433	    ssh_buffer sign_input = NULL;
2439	    if (session == NULL || privkey == NULL || !ssh_key_is_private(privkey)) {
2443	    crypto = session->next_crypto ? session->next_crypto :
2446	    if (crypto->secret_hash == NULL){
2452	    hash_type = ssh_key_type_to_hash(session, privkey->type);
[2019/10/27 22:39:59.532574, 3] ssh_key_algorithm_allowed:  Checking rsa-sha2-512 with list <rsa-sha2-512,rsa-sha2-256,ssh-rsa>
[2019/10/27 22:39:59.532626, 3] ssh_key_algorithm_allowed:  Checking rsa-sha2-256 with list <rsa-sha2-512,rsa-sha2-256,ssh-rsa>
2455	    sign_input = ssh_buffer_new();
(gdb) print hash_type

Notice how ssh-rsa was negotiated for hostkey verification. At this point, manually editing hash_type for SSH_DIGEST_SHA1 makes the key exchange complete successfully.
On the client side:

aris@ubuntu1804:~/git/libssh/build$ /home/aris/git/openssh-portable/ssh -o HostKeyAlgorithms=ssh-rsa,rsa-sha2-256 -vvv -p 2222 localhost id
OpenSSH_8.1p1, OpenSSL 1.1.1  11 Sep 2018
debug2: resolving "localhost" port 2222
debug2: ssh_connect_direct
debug1: Connecting to localhost [] port 2222.
debug1: Connection established.
debug1: identity file /home/aris/.ssh/id_rsa type -1
debug1: identity file /home/aris/.ssh/id_rsa-cert type -1
debug1: identity file /home/aris/.ssh/id_dsa type -1
debug1: identity file /home/aris/.ssh/id_dsa-cert type -1
debug1: identity file /home/aris/.ssh/id_ecdsa type -1
debug1: identity file /home/aris/.ssh/id_ecdsa-cert type -1
debug1: identity file /home/aris/.ssh/id_ed25519 type 3
debug1: identity file /home/aris/.ssh/id_ed25519-cert type -1
debug1: identity file /home/aris/.ssh/id_xmss type -1
debug1: identity file /home/aris/.ssh/id_xmss-cert type -1
debug1: Local version string SSH-2.0-OpenSSH_8.1
debug1: Remote protocol version 2.0, remote software version libssh_0.8.90
debug1: no match: libssh_0.8.90
debug2: fd 3 setting O_NONBLOCK
debug1: Authenticating to localhost:2222 as 'aris'
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,,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: ssh-rsa,rsa-sha2-256
debug2: ciphers ctos:,aes128-ctr,aes192-ctr,aes256-ctr,,
debug2: ciphers stoc:,aes128-ctr,aes192-ctr,aes256-ctr,,
debug2: MACs ctos:,,,,,,,hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: MACs stoc:,,,,,,,hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: compression ctos: none,,zlib
debug2: compression stoc: none,,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,,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1
debug2: host key algorithms: rsa-sha2-512,rsa-sha2-256,ssh-rsa
debug2: ciphers ctos:,,aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,3des-cbc
debug2: ciphers stoc:,,aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,3des-cbc
debug2: MACs ctos:,,,hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: MACs stoc:,,,hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: compression ctos: none
debug2: compression stoc: none
debug2: languages ctos: 
debug2: languages stoc: 
debug2: first_kex_follows 0 
debug2: reserved 0 
debug1: kex: algorithm: curve25519-sha256
debug1: kex: host key algorithm: ssh-rsa
debug1: kex: server->client cipher: aes128-ctr MAC: compression: none
debug1: kex: client->server cipher: aes128-ctr MAC: compression: none
debug3: send packet: type 30
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug3: receive packet: type 31
debug1: Server host key: ssh-rsa SHA256:qWF06d07f5gLdGcZgk9+SPBVv0HzSypYEApOQO8gsm4
debug3: put_host_port: []:2222
debug3: put_host_port: [localhost]:2222
debug3: hostkeys_foreach: reading file "/home/aris/.ssh/known_hosts"
debug3: record_hostkey: found key type RSA in file /home/aris/.ssh/known_hosts:3
debug3: load_hostkeys: loaded 1 keys from [localhost]:2222
debug1: Host '[localhost]:2222' is known and matches the RSA host key.
debug1: Found key in /home/aris/.ssh/known_hosts:3
ssh_dispatch_run_fatal: Connection to port 2222: incorrect signature

I found that the choice to use ssh-rsa-sha2-256 goes to ssh_key_type_to_hash, then ssh_key_algorithm_allowed(..., rsa-sha2-256) that then calls

int ssh_key_algorithm_allowed(ssh_session session, const char *type)
        allowed_list = session->opts.wanted_methods[SSH_HOSTKEYS];
    return ssh_match_group(allowed_list, type);

I don't understand why we go back to the list of our local preferences to decide if the peer supports it.

This code was last edited by @ansasaki, I take liberty to tag you in so you can have a look.

Event Timeline

aris created this task.Oct 27 2019, 11:01 PM

Thanks for reporting this.

The execution checks if the selected algorithm is allowed by the server configuration file, that is why it checks session->opts.wanted_methods. It is not checking if the peer supports it, but if we should use it.

I'll investigate further, probably the bug is a confusion when deciding which hash algorithm should be used when signing. For me it is unfortunate that ssh-rsa is the name for the RSA key type and also translates to RSA with SHA-1 when used as the signature type. There is no 1 : 1 relation between the key type and hash to be used, unlike the other key types.

fcharlie added a subscriber: fcharlie.EditedOct 29 2019, 1:36 PM

Putty seems to fail during the ssh key negotiation phase.

/opt/putty/bin/plink -i /opt/putty/keys/rsa.ppk -v -ssh git@localhost

TCP logs:

..q$.c..............Bye Bye...........

The SSH server report:

ssh_handle_key_exchange error: Could not sign the session id

After testing, this problem only occurs in libssh 0.9.1, and libssh 0.9.0 tests everything fine.

call stack chain:

ssh_handle_key_exchange -> ssh_packet_server_curve25519_init -> ssh_srv_pki_do_sign_sessionid
Jakuje added a subscriber: Jakuje.Oct 31 2019, 1:08 PM

This should be handled by the code in kex.c. It correctly sets the session->extensions bit field based on what is supported by the client in the key exchange. The problem here is that it informs only about the support of these extensions, but not about their priority and whether to prefer the SHA1 hash or the SHA2 ones. There is already attempt to detect whether the SHA256 or SHA519 was preferred, but this particular use case is not handled (what is the point in signaling that I know stronger algorithms, but prefer the SHA1?).

The problem is that in ssh_packet_kexinit() is called before ssh_kex_select_methods() so at the time of handling extensions, we still do not know what algorithm was actually selected. But later on in crypt_set_algorithms_server() we throw away this information and set just the key type here. I think the correct solution here is to record in crypt_set_algorithms_server() also the hash type and reuse it in the argument to the ssh_srv_pki_do_sign_sessionid() function.

See the proof of concept, that might need some more polishing, but your use case seems to be solved with it:

I am not sure how is this related to the putty comment, which does not support the extension negotiation and therefore it should not attempt to use SHA2 with RSA at all. I think we would need more debug information.

fcharlie added a comment.EditedNov 1 2019, 2:40 AM

@Jakuje Thanks for your code, but I found it seems that EVP_DigestSignUpdate reported an error

The errors reported by libssh-0.9.1 and libssh-master-fix are the same

// EVP_DigestSignUpdate
error:060AD0B1:digital envelope routines:update:only oneshot supported

When I delete ed25519 host_key in the ssh server, putty can correctly establish a combo.

Sounds like an issue with the ed25519 keys then. Can you open a separate issue, since this is indeed different one than the reported above (RSA keys). Clarifying what openssl version are you using and whether it has enable support for ED25519 keys would help.