SSH握手报错缺少主机密钥。

huangapple go评论75阅读模式
英文:

SSH Handshake complains about missing host key

问题

我正在尝试连接到远程主机并检查文件是否存在。目前我只是尝试连接,但是我遇到了一个错误:

2017/08/01 18:16:39 无法连接:ssh:握手失败:ssh:所需的主机密钥为nil

我尝试查找是否有其他人遇到了类似的问题,但是我找不到。

我理解在这个过程中需要检查known_hosts文件,但是我无法弄清楚如何做到...

var hostKey ssh.PublicKey
// 可以使用公钥通过使用未加密的PEM编码的私钥文件进行对远程服务器进行身份验证。
//
// 如果您有一个加密的私钥,可以使用crypto/x509包来解密它。
key, err := ioutil.ReadFile("/home/user/.ssh/id_rsa")
if err != nil {
    log.Fatalf("无法读取私钥:%v", err)
}

// 为此私钥创建签名者。
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
    log.Fatalf("无法解析私钥:%v", err)
}

config := &ssh.ClientConfig{
    User: "user",
    Auth: []ssh.AuthMethod{
        // 使用PublicKeys方法进行远程身份验证。
        ssh.PublicKeys(signer),
    },
    HostKeyCallback: ssh.FixedHostKey(hostKey),
}

// 连接到远程服务器并执行SSH握手。
client, err := ssh.Dial("tcp", "host.com:22", config)
if err != nil {
    log.Fatalf("无法连接:%v", err)
}
defer client.Close()
英文:

I'm trying to connect to a remote host and check if a file exist
At this stage I'm trying just to connect but I'm getting an error:

2017/08/01 18:16:39 unable to connect: ssh: handshake failed: ssh: required host key was nil

I've tried to find out if others had issues as mine but I just couldn't find.

I understand that I need to check the knowns_hosts somehow in the process but I just can't figure out how...

	var hostKey ssh.PublicKey
	// A public key may be used to authenticate against the remote
	// server by using an unencrypted PEM-encoded private key file.
	//
	// If you have an encrypted private key, the crypto/x509 package
	// can be used to decrypt it.
	key, err := ioutil.ReadFile("/home/user/.ssh/id_rsa")
	if err != nil {
		log.Fatalf("unable to read private key: %v", err)
	}

	// Create the Signer for this private key.
	signer, err := ssh.ParsePrivateKey(key)
	if err != nil {
		log.Fatalf("unable to parse private key: %v", err)
	}

	config := &ssh.ClientConfig{
		User: "user",
		Auth: []ssh.AuthMethod{
			// Use the PublicKeys method for remote authentication.
			ssh.PublicKeys(signer),
		},
		HostKeyCallback: ssh.FixedHostKey(hostKey),
	}

	// Connect to the remote server and perform the SSH handshake.
	client, err := ssh.Dial("tcp", "host.com:22", config)
	if err != nil {
		log.Fatalf("unable to connect: %v", err)
	}
	defer client.Close()
}

答案1

得分: 12

我建议使用knownhosts子包。

import knownhosts "golang.org/x/crypto/ssh/knownhosts"

...

hostKeyCallback, err := knownhosts.New("/Users/user/.ssh/known_hosts")
if err != nil {
    log.Fatal(err)
}

...

config := &ssh.ClientConfig{
    User: "user",
    Auth: []ssh.AuthMethod{
        // Use the PublicKeys method for remote authentication.
        ssh.PublicKeys(signer),
    },
    HostKeyCallback: hostKeyCallback,
}

这样你就可以避免自己解析known_hosts文件了。

希望对你有帮助!

英文:

I would suggest to use knownhosts subpackage

import knownhosts "golang.org/x/crypto/ssh/knownhosts"

...

hostKeyCallback, err := knownhosts.New("/Users/user/.ssh/known_hosts")
if err != nil {
	log.Fatal(err)
}

...

config := &ssh.ClientConfig{
    User: "user",
    Auth: []ssh.AuthMethod{
        // Use the PublicKeys method for remote authentication.
        ssh.PublicKeys(signer),
    },
    HostKeyCallback: hostKeyCallback,
}

So that you avoid parsing known_hosts yourself...

hth,

答案2

得分: 9

这是你要找的内容:

func getHostKey(host string) (ssh.PublicKey, error) {
    file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
    if err != nil {
        return nil, err
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    var hostKey ssh.PublicKey
    for scanner.Scan() {
        fields := strings.Split(scanner.Text(), " ")
        if len(fields) != 3 {
            continue
        }
        if strings.Contains(fields[0], host) {
            var err error
            hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
            if err != nil {
                return nil, errors.New(fmt.Sprintf("error parsing %q: %v", fields[2], err))
            }
            break
        }
    }

    if hostKey == nil {
        return nil, errors.New(fmt.Sprintf("no hostkey for %s", host))
    }
    return hostKey, nil
}

Then replace your hostKey definition line with

hostKey, err := getHostKey("host.com")
if err != nil {
    log.Fatal(err)
}

For more information on the subject:

- [official sample](https://github.com/golang/crypto/blob/master/ssh/example_test.go) where I took parts of the code from
- [why a hostKey is necessary now](https://github.com/golang/go/issues/19767)

EDIT:
Also check out `Anton`'s answer below about the `golang.org/x/crypto/ssh/knownhosts` package.

然后将你的hostKey定义行替换为

hostKey, err := getHostKey("host.com")
if err != nil {
    log.Fatal(err)
}

有关此主题的更多信息:

编辑:
还请查看下面Anton关于golang.org/x/crypto/ssh/knownhosts包的回答。

英文:

Here what you are looking for:

func getHostKey(host string) (ssh.PublicKey, error) {
file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
var hostKey ssh.PublicKey
for scanner.Scan() {
fields := strings.Split(scanner.Text(), " ")
if len(fields) != 3 {
continue
}
if strings.Contains(fields[0], host) {
var err error
hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
if err != nil {
return nil, errors.New(fmt.Sprintf("error parsing %q: %v", fields[2], err))
}
break
}
}
if hostKey == nil {
return nil, errors.New(fmt.Sprintf("no hostkey for %s", host))
}
return hostKey, nil
}

Then replace your hostKey definition line with

hostKey, err := getHostKey("host.com")
if err != nil {
log.Fatal(err)
}

For more information on the subject:

EDIT:
Also check out Anton's answer below about the golang.org/x/crypto/ssh/knownhosts package.

答案3

得分: 0

我建议使用knownhosts子包。

请注意,从Go 1.12到1.20(2023年第一季度),你可能会遇到以下错误:

unable to connect: ssh: handshake failed: ssh: no authorities for hostname

请参考x/crypto/ssh/knownhosts: can't verify host key if host certificate is sent #33366

我认为这是由于https://github.com/golang/crypto/blob/dab2b10/ssh/certs.go#L304未对cert.Keyc.HostKeyFallback进行检查所导致的。

可能的修补方法:

        if !c.IsHostAuthority(cert.SignatureKey, addr) {
+               if c.HostKeyFallback != nil {
+                       err := c.HostKeyFallback(addr, remote, cert.Key)
+                       if err != nil {
+                               return fmt.Errorf("ssh: no authorities for hostname %v. retry with plain key but failed: %v", addr, err)
+                       } else {
+                               return nil
+                       }
+               }
                return fmt.Errorf("ssh: no authorities for hostname: %v", addr)
        }
英文:

> I would suggest to use knownhosts subpackage

Note that, from Go 1.12 to 1.20 (Q1 2023), you can get:

unable to connect: ssh: handshake failed: ssh: no authorities for hostname

See x/crypto/ssh/knownhosts: can't verify host key if host certificate is sent #33366

> I think this happens due to https://github.com/golang/crypto/blob/dab2b10/ssh/certs.go#L304 not checking cert.Key against c.HostKeyFallback.

Possible patch:

        if !c.IsHostAuthority(cert.SignatureKey, addr) {
+               if c.HostKeyFallback != nil {
+                       err := c.HostKeyFallback(addr, remote, cert.Key)
+                       if err != nil {
+                               return fmt.Errorf("ssh: no authorities for hostname %v. retry with plain key but failed: %v", addr, err)
+                       } else {
+                               return nil
+                       }
+               }
                return fmt.Errorf("ssh: no authorities for hostname: %v", addr)
        }

huangapple
  • 本文由 发表于 2017年8月1日 23:20:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/45441735.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定