skeema / knownhosts

Go SSH known_hosts wrapper with host key lookup
Apache License 2.0
32 stars 4 forks source link

If the key not found in known_host then OpenSSH adds all the host keys to it. #12

Closed abakum closed 4 months ago

abakum commented 4 months ago

This behavior can be done in your example by adding after the line https://github.com/skeema/knownhosts/blob/9485bdec8ed521b32ab0e9310619effa071a99d6/example_test.go#L109

                if ferr == nil {
                    for _, key := range ScanHostKeys(hostname, key.Type()) {
                        ferr = knownhosts.WriteKnownHost(f, hostname, remote, key)
                        if ferr != nil {
                            break
                        }
                    }
                }

ScanHostKeys:

func ScanHostKeys(hostPort, firstHostKeyAlgorithm string) (HostPublicKeys []ssh.PublicKey) {
    const (
        BadAlgoritm = "no such algorithm"
        TO          = time.Second * 2
    )
    KeyScanCallback := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
        HostPublicKeys = append(HostPublicKeys, key)
        return fmt.Errorf(BadAlgoritm)
    }
    config := &ssh.ClientConfig{
        HostKeyCallback:   KeyScanCallback,
        HostKeyAlgorithms: []string{BadAlgoritm},
        Timeout:           TO,
    }
    // Get HostKeyAlgorithms.
    client, err := ssh.Dial("tcp", hostPort, config)
    if err != nil {
        ss := strings.Split(err.Error(), "server offered: [")
        if len(ss) < 2 {
            return
        }
        ss = strings.Split(ss[1], "]")
        if len(ss) < 2 {
            return
        }
        HostKeyAlgorithms := strings.Fields(ss[0])

        // Do not search first algorithm.
        CertAlgoRSA := false
        KeyAlgoRSA := false
        switch firstHostKeyAlgorithm {
        case ssh.CertAlgoRSASHA256v01, ssh.CertAlgoRSASHA512v01, ssh.CertAlgoRSAv01:
            CertAlgoRSA = true
        case ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512, ssh.KeyAlgoRSA:
            KeyAlgoRSA = true
        }
        for _, HostKeyAlgorithm := range HostKeyAlgorithms {
            switch HostKeyAlgorithm {
            case ssh.CertAlgoRSASHA256v01, ssh.CertAlgoRSASHA512v01, ssh.CertAlgoRSAv01:
                if CertAlgoRSA {
                    continue
                }
                CertAlgoRSA = true
            case ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512, ssh.KeyAlgoRSA:
                if KeyAlgoRSA {
                    continue
                }
                KeyAlgoRSA = true
            default:
                if HostKeyAlgorithm == firstHostKeyAlgorithm {
                    continue
                }
            }
            // This is not first algoritm.
            config.HostKeyAlgorithms = []string{HostKeyAlgorithm}
            client, err := ssh.Dial("tcp", hostPort, config)
            if err != nil {
                continue
            }
            client.Close()
        }
        return
    }
    client.Close()
    return
}

Will you accept PR?

evanelias commented 4 months ago

Interesting work-around, but ultimately a duplicate of #5. The solution I proposed there involved reconnecting N times for N keys; your solution here is better in some ways (yours only requires 1 extra connection), but may be more brittle since it requires parsing error text, which is generally inadvisable in Go.

Either way, since this is involving extra connections and is handled outside of this package, I'd rather keep the official example small and simple.

Will you accept PR?

Afraid I've been spending way too much time on this repo lately. Unless there's a major issue, I need to drop my time on this repo down to zero for the foreseeable future, sorry.