Repository: go
Issue: proposal: x/crypto/ssh: verified public key callback and arbitrary data in Permissions
Labels: Proposal,Proposal-Crypto

### Proposal Details

The `ServerConfig.PublicKeyCallback` callback gets called as part of the server-side SSH handshake whenever the client tries to authenticate with a public key or when the client asks whether or not a public key would be accepted by the server. No matter the order in which keys are presented, and notwithstanding the mitigation for CVE-2024-45337 (#70779), the callback is called before any signature from the client is verified (or potentially even received), which means that it's not practical or advisable to have the server make decisions on whether or not to continue with the authentication (potentially with a partial success) based on data and computations that require more effort than what can be allowed for an unauthenticated and untrusted client.

I propose the addition of a new, optional callback to `ssh.ServerConfig`:

```go
type ServerConfig struct {
	...
	VerifiedPublicKeyCallback func(ConnMetadata, PublicKey, *Permissions) (*Permissions, error)
}
```

If `PublicKeyCallback` returns no error for a given public key, the client provides a `SSH_MSG_USERAUTH_REQUEST` message with a valid signature for that public key, and `VerifiedPublicKeyCallback` is non-nil, the `VerifiedPublicKeyCallback` will be called before the server sends a response to the client, passing in the same `ConnMetadata` and `PublicKey` that was passed to the `PublicKeyCallback`, and the `*Permissions` object that was returned by `PublicKeyCallback`.

The `VerifiedPublicKeyCallback` can then return the same or a new `*Permissions` to complete the handshake, a `*PartialSuccessError` to continue the handshake with a partial success and new authentication callbacks, or a different non-nil error to fail the authentication and let the client attempt to use a different authentication method (I believe that according to RFC 4252 the server is allowed to fail the authentication for a key that it has acknowledged as acceptable earlier in the handshake, but it might be worth checking the behavior of popular SSH clients in such a situation). `VerifiedPublicKeyCallback` is guaranteed to not be called again after returning success - in case of a `*PartialSuccessError` that includes `PublicKeyCallback`, it might be called again once more for a different public key.

To avoid ambiguous behavior, `PublicKeyCallback` is not allowed to return a `*PartialSuccessError` if `VerifiedPublicKeyCallback` is non-nil.

To better support passing data between the two callbacks - and to allow for extracting richer authentication data without risking the sort of misuse that led to CVE-2024-45337 - I also propose that we extend `Permissions` to include a single `any` field, for use by the authentication callbacks:

```go
type Permissions struct {
	CriticalOptions map[string]string

	Extensions map[string]string

	ExtraData any
}
```

The `ExtraData` field will be available for inspection in `ServerConn.Permissions` after the handshake, and it can be used to pass some arbitrary data from `PublicKeyCallback` to `VerifiedPublicKeyCallback`.