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: error from ssh.HostKeyCallback is not wrapped during handshake #61309

Closed
paxan opened this issue Jul 12, 2023 · 4 comments
Closed
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Milestone

Comments

@paxan
Copy link

paxan commented Jul 12, 2023

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

$ go version
go version go1.20.5 darwin/arm64

Does this issue reproduce with the latest release?

Yes.

Root cause

https://github.com/golang/crypto/blob/2e82bdd17/ssh/client.go#L85

return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err)

It does not wrap the causing err using %w.

What did you do?

In my program, there is a mechanism that, for a given host, maintains []ssh.PublicKey with known host keys.

With this slice in hands, we proceed to call ssh.NewClientConn with:

ssh.ClientConfig{
    HostKeyCallback: validateHostKeys(known),
    ...
}

During ssh handshake if the host key doesn't match any keys from that slice, we get an error:

ssh: handshake failed: unknown host key: ecdsa-sha2-nistp256 AAAAElided...

The "unknown host key: ..." part comes from function validateHostKeys:

var ErrUnknownHostKey = errors.New("unknown host key")

func validateHostKeys(knownHostKeys []ssh.PublicKey) ssh.HostKeyCallback {
	return func(hostname string, remote net.Addr, hostKey ssh.PublicKey) error {
		if hostKey == nil {
			return fmt.Errorf("got a nil host key")
		}

		for _, knownHostKey := range knownHostKeys {
			if bytes.Equal(hostKey.Marshal(), knownHostKey.Marshal()) {
				return nil
			}
		}

		return fmt.Errorf("%w: %s",
			ErrUnknownHostKey, bytes.TrimSpace(ssh.MarshalAuthorizedKey(hostKey)))
	}
}

What did you expect to see?

In my host validation error handler, I am using:

errors.Is(err, ErrUnknownHostKey)

... and expect it to return true when such error indeed arises.

What did you see instead?

It returns false.

@gopherbot gopherbot added this to the Unreleased milestone Jul 12, 2023
paxan added a commit to paxan/crypto that referenced this issue Jul 12, 2023
When an error is returned by a user defined host key callback,
it is now possible to handle it using standard Go mechanisms
such as errors.Is or errors.As.

Fixes golang/go#61309
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/508876 mentions this issue: ssh: wrap errors from client handshake

@cherrymui cherrymui added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jul 12, 2023
@cherrymui
Copy link
Member

cc @FiloSottile @golang/security

@drakkan
Copy link
Member

drakkan commented Jul 12, 2023

Hello,

this is a good addition but I would like to see something more generic.

For example I'm exposing ErrNoCommonAlgo in my branch. Tailscale exposes other errors in their branch and so on.

I think we should expose an error struct with an error code or something similar so that we can let users easily identify the various types of errors. Obviously this struct should support errors.Is/As

@paxan
Copy link
Author

paxan commented Jul 12, 2023

Having a dedicated ssh error struct would be very valuable, agreed!
But it doesn’t clash with this use case: the package user is supplying a callback that returns a user-supplied error, so it is natural to expect that this error should be wrapped correctly, isn’t it?

@dmitshur dmitshur added NeedsFix The path to resolution is known, but the work has not been done. and removed NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Nov 27, 2023
@golang golang locked and limited conversation to collaborators Nov 26, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants