This site is being rebuilt and some pages are out of date. For current details, write to [email protected]. This notice goes away when the rebuild is done.

No analytics unless you allow it, no tracking. This site keeps in your browser the language you pick, the theme, its colour, which site you chose, the currency on the pricing page and that you closed this notice; signing in adds session cookies. The legal page has the details.

Sign in

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 ServerConfig type now includes a VerifiedPublicKeyCallback field that is invoked after a client successfully proves control over a key previously approved by PublicKeyCallback. The callback receives the Permissions object returned by PublicKeyCallback and may return a modified or new Permissions object. PublicKeyCallback is no longer allowed to return a PartialSuccessError; that error can only be returned by VerifiedPublicKeyCallback.
Production impact
The source does not say.
Try it
Create a ServerConfig with 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

Todaygo
// 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