Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
julianbrost opened this issue Jul 30, 2019 · 5 comments
Open
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@julianbrost
Copy link

What version of Go are you using (go version)?

$ go version
go version go1.12.7 linux/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/julian/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/julian/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/lib/go"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build549038278=/tmp/go-build -gno-record-gcc-switches"

What did you do?

Write the following single line to a file called known_hosts:

faui06.informatik.uni-erlangen.de ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8Mt0PH9D8QxhTRy3LezMhwKSp2l5D5FsljhbDU4NhRH7PP668zdPBfOMoJXNDOHSzKcs4X2K4P5eNVyWhFU7jJwdLDCtetnZWRA984jwmBrWUGOZXpuxs72wjHVfnYp5npq3LzbYUPQ6FzdVmHsWHy/SW1OW28xNP1Z4JhHysqcnZpuVT7wOvgpQ81ltpbnqEEkMez39mwin044CfdpjDQUKSYjySsxexX9wrZqMD4CfwB0D/Y5T/sZToHO8UURnxIw08SMOxn7VwGFFv1F6AhDu9T7Pd/9aWMP0djY/WWIJwB6iAhmalPcdEA88uHBar5Zwbo6yKusmRb0JjiKkb

Run the following Go code (in the same directory so that it finds the file or change the path accordingly):

package main

import (
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/knownhosts"
	"log"
)

func main() {
	hostKeyCallback, err := knownhosts.New("known_hosts")
	if err != nil {
		log.Fatalf("unable to read ssh known hosts file: %v", err)
	}

	config := &ssh.ClientConfig{
		User: "nobody",
		HostKeyCallback:   hostKeyCallback,
		// HostKeyAlgorithms: []string{ssh.KeyAlgoRSA},
	}

	client, err := ssh.Dial("tcp", "faui06.informatik.uni-erlangen.de:22", config)
	if err != nil {
		log.Fatalf("unable to connect: %v", err)
	}
	defer client.Close()
}

What did you expect to see?

The host key can be verified successfully.

Due to the minimal example which omits any authentication, there will also be an error in the successful case, but a different one:

unable to connect: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none], no supported methods remain

Note that the connection works fine if the HostKeyAlgorithms from the code is uncommitted, which disables requesting host certificates. I found this behavior quite surprising and it took me some time to figure this out. Also this is inconsistent with OpenSSH which, if it receives a host certificate, seems to extract the host public key from it and also check this against the known hosts file.

What did you see instead?

The host key can't be verified and the program exits with this error message:

unable to connect: ssh: handshake failed: ssh: no authorities for hostname: faui06.informatik.uni-erlangen.de:22
@gopherbot gopherbot added this to the Unreleased milestone Jul 30, 2019
@julianbrost
Copy link
Author

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

@katiehockman
Copy link
Contributor

/cc @FiloSottile

@Bogay
Copy link

Bogay commented May 10, 2023

Hi, I encountered this issue and found that this patch fixes it. It would fallback to verify cert.Key. Although I am not sure if this behavior is standard. But when I try to connect to server using ssh -v. I can see debug1: No matching CA found. Retry with plain key in the outputs. So I think it's intended.

        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)
        }

If this is the correct way to fix, I can add some tests to it and open a PR.

@tweakker
Copy link

Faced with the same problem. Research lead to similar solution as @Bogay described. But with small corrections: c.HostKeyFallback returns the KeyError which is useful for separating 2 cases: 1) line isn't exists in known_hosts file 2) key is wrong. So just return result of c.HostKeyFallback(...) will be better.

...
if !c.IsHostAuthority(cert.SignatureKey, addr) {
    if c.HostKeyFallback != nil {
        return c.HostKeyFallback(addr, remote, cert.Key)
    }
    return fmt.Errorf("ssh: no authorities for hostname: %v", addr)
}
...

@Bogay
Copy link

Bogay commented Oct 20, 2023

Faced with the same problem. Research lead to similar solution as @Bogay described. But with small corrections: c.HostKeyFallback returns the KeyError which is useful for separating 2 cases: 1) line isn't exists in known_hosts file 2) key is wrong. So just return result of c.HostKeyFallback(...) will be better.

But in this approach, we don't provide any additional information that the fallback is being used (i.e., what I added with fmt.Errorf). Maybe we can embed the returned KeyError using the %w verb so that it can be unwrapped if needed? I am not familar with this usage, please correct me if I misunderstand it.

If the format specifier includes a %w verb with an error operand, the returned error will implement an Unwrap method returning the operand. (ref)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

5 participants