fatedier/frp · error

selected crypto algorithm was not advertised by client: %s

Error message

selected crypto algorithm was not advertised by client: %s

What it means

Thrown by ValidateServerHelloForClient when the server selects a crypto algorithm that, while known, was never offered by this client in ClientHello.Capabilities.Crypto.Algorithms. It is an anti-downgrade / anti-surprise guard: the server must pick from what the client advertised.

Source

Thrown at pkg/proto/wire/crypto.go:110

func ValidateServerHelloForClient(clientHello ClientHello, serverHello ServerHello) error {
	if serverHello.Selected.Message.Codec != MessageCodecJSON {
		return fmt.Errorf("unsupported selected message codec: %s", serverHello.Selected.Message.Codec)
	}
	udpPacketCodec := serverHello.Selected.Message.UDPPacketCodec
	if udpPacketCodec != "" {
		if udpPacketCodec != UDPPacketCodecBinary {
			return fmt.Errorf("unsupported selected UDP packet codec: %s", udpPacketCodec)
		}
		if !Supports(clientHello.Capabilities.Message.UDPPacketCodecs, udpPacketCodec) {
			return fmt.Errorf("selected UDP packet codec was not advertised by client: %s", udpPacketCodec)
		}
	}
	cryptoSelection := serverHello.Selected.Crypto
	if !IsSupportedAEADAlgorithm(cryptoSelection.Algorithm) {
		return fmt.Errorf("unknown selected crypto algorithm: %s", cryptoSelection.Algorithm)
	}
	if !Supports(clientHello.Capabilities.Crypto.Algorithms, cryptoSelection.Algorithm) {
		return fmt.Errorf("selected crypto algorithm was not advertised by client: %s", cryptoSelection.Algorithm)
	}
	if len(cryptoSelection.ServerRandom) != CryptoRandomSize {
		return fmt.Errorf("invalid crypto server random length %d, want %d", len(cryptoSelection.ServerRandom), CryptoRandomSize)
	}
	return nil
}

func selectUDPPacketCodec(codecs []string) string {
	if Supports(codecs, UDPPacketCodecBinary) {
		return UDPPacketCodecBinary
	}
	return ""
}

func NewCryptoContext(algorithm string, clientHelloPayload, serverHelloPayload []byte) *CryptoContext {
	return &CryptoContext{
		Algorithm:      algorithm,
		TranscriptHash: HashCryptoTranscript(clientHelloPayload, serverHelloPayload),

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. On the server, always select through SelectAEADAlgorithm(clientHello.Capabilities.Crypto.Algorithms) which iterates the client's own list.
  2. On the client, send the full PreferredAEADAlgorithms() list so any reasonable server choice is covered.
  3. Regenerate paired hello fixtures from a single handshake helper in tests.

Example fix

// before (client, restrictive)
hello.Capabilities.Crypto.Algorithms = []string{"xchacha20-poly1305"}

// after
hello.Capabilities.Crypto.Algorithms = wire.PreferredAEADAlgorithms()
Defensive patterns

Strategy: validation

Validate before calling

offered := clientHello.Capabilities.Crypto.Algorithms
selected := serverHello.Selected.Crypto.Algorithm
if !wire.Supports(offered, selected) {
    return fmt.Errorf("server selected %q which client never offered", selected)
}

Prevention

When it happens

Trigger: Client advertises only ["xchacha20-poly1305"] but the server responds with Selected.Crypto.Algorithm = "aes-256-gcm" — the algorithm is valid but Supports(clientHello.Capabilities.Crypto.Algorithms, ...) is false.

Common situations: Server-side selection logic that ignores client capabilities (e.g. always preferring aes-256-gcm on hardware with AES-NI); test fixtures where client and server hellos are constructed separately; a tampering middlebox changing the algorithm field.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/d49e23fa6233a841. Report an issue: GitHub.