Description
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
:
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:
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
.
Metadata
Metadata
Assignees
Type
Projects
Status