3
votes

I'm trying to connect to a remote host to issue a command, but I'm getting the following error message while running the code:

ssh: handshake failed: ssh: no common algorithm for key exchange; client offered: [[email protected] ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 diffie-hellman-group14-sha1], server offered: [diffie-hellman-group1-sha1]panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x759836]

Here is the code that I'm using:

func (SSHClient *SSH) Connect(mode int) {
    var SSHConfig *ssh.ClientConfig
    var auth []ssh.AuthMethod

    if mode == CERT_PUBLIC_KEY_FILE {
        auth = []ssh.AuthMethod{SSHClient.readPublicKeyFile(SSHClient.Cert)}
    }

    SSHConfig = &ssh.ClientConfig{
        User:            SSHClient.User,
        Auth:            auth,
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
        Timeout:         time.Second * DEFAULT_TIMEOUT,
    }

    SSHConfig.Config.Ciphers = append(SSHConfig.Config.Ciphers, "diffie-hellman-group1-sha1")

    client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", SSHClient.IP, SSHClient.Port), SSHConfig)

    if err != nil {
        fmt.Printf("ERROR - While trying to Dial to the host %s with error: %s", SSHClient.IP, err.Error())
        return
    }

    session, err := client.NewSession()
    if err != nil {
        fmt.Printf("ERROR - While trying to create a new session on host %s with error: %s", SSHClient.IP, err.Error())
        client.Close()
        return
    }

    SSHClient.session = session
    SSHClient.client = client
}

Any ideas on how to resolve this issue?

Thanks in advance.

3
From the error, it looks like a cipher mismatch i.e. diffie-hellman-group14-sha1 vs diffie-hellman-group1-sha1. I'm not sure why you're getting that panic there despite the fact that all the errors are already handled.Azeem
+1 for asking a SSH question that has to do with programming and development.jww

3 Answers

2
votes

The problem is.... the server is only willing to talk over diffie-hellman-group1-sha1

And:

  • golang/go issue 2903: ssh: add diffie-hellman-group1-sha1, has been closed 6 days ago
  • golang/go/issue 17230: proposal: x/crypto/ssh: support Diffie-Hellman Group Exchange from RFC 4419, is being implemented now.

So you would need for your client a fork of golang.org/x/crypto/ssh, like bored-engineer/ssh, where commit 39a91b and commit fe5e4ff does add support for diffie-hellman-group1-sha1.
Or install the latest of golang/crypto, which includes commit 57b3e21.

0
votes

The panic is somewhat strange. Clearly something goes wrong when no key exchange algorithm can be agreed-to. As VonC notes, Diffie-Helman key exchange was only added fairly recently (June 3). Since your server offers only that algorithm, you can't get started without it.

This is not the cause of the panic (which seems to happen inside ssh.Dial itself), but I will note that when you do this:

SSHConfig.Config.Ciphers = append(SSHConfig.Config.Ciphers, "diffie-hellman-group1-sha1")

you wind up telling the Go code to only use diffie-helman-group1-sha1 as the channel encryption. You do not add to anything here. The reason is that SSHConfig.Config.Ciphers is initially nil. So you might as well write:

SSHConfig.Config.Ciphers = []string{"diffie-hellman-group1-sha1"}

to get the same effect, which is: things won't work.

You can call SetDefaults so that the list is non-empty before adding to the list, but adding to the list is ineffective if there is no implementation for this mode—and even with the new commits, Diffie-Helman isn't allowed for anything other than the key exchange itself. Note that ssh.Dial calls ssh.NewClientConn, which is here and which starts with:

fullConf := *config
fullConf.SetDefaults()

SetDefaults in turn is here and contains:

if c.Ciphers == nil {
    c.Ciphers = preferredCiphers
}
var ciphers []string
for _, c := range c.Ciphers {
    if cipherModes[c] != nil {
        // reject the cipher if we have no cipherModes definition
        ciphers = append(ciphers, c)
    }
}
c.Ciphers = ciphers

which first says that if the config's Ciphers is not set, it should use the defaults, and then immediately after that, filters away any string that's not in cipherModes. This in turn is defined here and starts with this comment:

// cipherModes documents properties of supported ciphers. Ciphers not included
// are not supported and will not be negotiated, even if explicitly requested in
// ClientConfig.Crypto.Ciphers.

This phrase is not in the documentation. It should be! Ciphers not included are not supported and will not be negotiated, even if explicitly requested in ClientConfig.Crypto.Ciphers.

(See the last link above for the set of ciphers that are supported. Note that this list has grown over time.)

0
votes

Diffie-hellman-group1-sha1 is a key exchange algorithm. Should be KeyExchanges instead of Ciphers in the Config struct

SSHConfig.Config.KeyExchanges = append(SSHConfig.Config.KeyExchanges, "diffie-hellman-group1-sha1")

Instead of

SSHConfig.Config.Ciphers = append(SSHConfig.Config.Ciphers, "diffie-hellman-group1-sha1")

If KeyExchanges is not specified, the default algorithms used can be found in ssh/common.go

// preferredKexAlgos specifies the default preference for key-exchange algorithms
// in preference order.
var preferredKexAlgos = []string{
    kexAlgoCurve25519SHA256,
    kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
    kexAlgoDH14SHA1,
}

As you can see, kexAlgoDH1SHA1 or diffie-hellman-group1-sha1 is not listed at this time