Radar · Go · Archive · Week 28 · Jul 7 – 13, 2025
x/crypto/ssh: verified public key callback and arbitrary data in Permissions
Worth knowingstdlib
- What changed
- The
ServerConfigtype now includes aVerifiedPublicKeyCallbackfield that is invoked after a client successfully proves control over a key previously approved byPublicKeyCallback. The callback receives thePermissionsobject returned byPublicKeyCallbackand may return a modified or newPermissionsobject.PublicKeyCallbackis no longer allowed to return aPartialSuccessError; that error can only be returned byVerifiedPublicKeyCallback. - Production impact
- The source does not say.
- Try it
- Create a
ServerConfigwith both callbacks and observe the order of invocation during a key‑based SSH session. - Source
- github.com/golang/go/issues/70795
Understand it, then run it
The SSH package lets a server decide whether a client can log in with a public key. Previously the only hook was PublicKeyCallback, which ran before the client sent a signature. Now a new optional hook, VerifiedPublicKeyCallback, runs after the client proves it owns the key. It receives the same Permissions object that PublicKeyCallback returned and can modify or replace it. PublicKeyCallback can no longer return a PartialSuccessError; that error is now only allowed from VerifiedPublicKeyCallback.
Run it now
// This program demonstrates the intended behaviour of the new
// `VerifiedPublicKeyCallback` and the `ExtraData` field in `Permissions`.
// Since the change is not yet in Go 1.27.1, the code below uses mock
// types that mimic the relevant parts of golang.org/x/crypto/ssh.
// Running this program prints the sequence of callbacks and the
// data passed between them.
package main
import (
"fmt"
)
// Mock types that resemble the real ssh package structures.
type ConnMetadata struct{ ID int }
type PublicKey struct{ Key string }
type Permissions struct {
CriticalOptions map[string]string
Extensions map[string]string
ExtraData any
}
type PartialSuccessError struct{ msg string }
func (e *PartialSuccessError) Error() string { return e.msg }
// ServerConfig holds the callbacks.
type ServerConfig struct {
PublicKeyCallback func(ConnMetadata, PublicKey) (*Permissions, error)
VerifiedPublicKeyCallback func(ConnMetadata, PublicKey, *Permissions) (*Permissions, error)
}
// Simulate an SSH handshake that triggers the callbacks.
func simulateHandshake(cfg ServerConfig, meta ConnMetadata, key PublicKey) {
// Step 1: PublicKeyCallback is called.
perms, err := cfg.PublicKeyCallback(meta, key)
if err != nil {
fmt.Println("PublicKeyCallback error:", err)
return
}
fmt.Println("PublicKeyCallback returned permissions:", perms)
// Step 2: If a signature is provided (simulated here), call VerifiedPublicKeyCallback.
if cfg.VerifiedPublicKeyCallback != nil {
newPerms, err := cfg.VerifiedPublicKeyCallback(meta, key, perms)
if err != nil {
fmt.Println("VerifiedPublicKeyCallback error:", err)
return
}
fmt.Println("VerifiedPublicKeyCallback returned permissions:", newPerms)
}
}
func main() {
// Define the callbacks.
cfg := ServerConfig{
PublicKeyCallback: func(meta ConnMetadata, key PublicKey) (*Permissions, error) {
// Pretend we decide the key is acceptable and attach some data.
perms := &Permissions{
CriticalOptions: map[string]string{"no-port-forwarding": ""},
Extensions: map[string]string{"permit-agent-forwarding": ""},
ExtraData: fmt.Sprintf("user-%d", meta.ID),
}
return perms, nil
},
VerifiedPublicKeyCallback: func(meta ConnMetadata, key PublicKey, perms *Permissions) (*Permissions, error) {
// Use the data from PublicKeyCallback and modify permissions.
if perms.ExtraData == nil {
return nil, &PartialSuccessError{"missing extra data"}
}
// Append a new critical option based on the extra data.
perms.CriticalOptions["permit-pty"] = ""
return perms, nil
},
}
// Run the simulation.
meta := ConnMetadata{ID: 42}
key := PublicKey{Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC..."}
simulateHandshake(cfg, meta, key)
}
What it printed when we ran it on Go 1.27.1
PublicKeyCallback returned permissions: &{map[no-port-forwarding:] map[permit-agent-forwarding:] user-42}
VerifiedPublicKeyCallback returned permissions: &{map[no-port-forwarding: permit-pty:] map[permit-agent-forwarding:] user-42}
Run sends this program (for Solidity, the contract and its tests) to our own sandbox, where it is compiled and run once, with no network, and what it printed or the test report comes back here. Nothing is kept. Runs are counted per visitor for the day so everyone gets a turn; the details are on the legal page.
Written by gpt-oss-20b from the linked source · claims checked against the sources · archive, not individually reviewed