golang/go · error

tls: unexpected ServerKeyExchange

Error message

tls: unexpected ServerKeyExchange

What it means

The rsaKeyAgreement implementation's processServerKeyExchange unconditionally returns this error because RSA key exchange (as defined in TLS 1.0–1.2) never involves a ServerKeyExchange message — the client encrypts the pre-master secret directly to the server's certificate public key. Receiving a ServerKeyExchange when RSA key exchange was negotiated means the handshake is malformed.

Source

Thrown at src/crypto/tls/key_agreement.go:79

	if !ok {
		return nil, errors.New("tls: certificate private key does not implement crypto.Decrypter")
	}
	// Perform constant time RSA PKCS #1 v1.5 decryption
	preMasterSecret, err := priv.Decrypt(config.rand(), ciphertext, &rsa.PKCS1v15DecryptOptions{SessionKeyLen: 48})
	if err != nil {
		return nil, err
	}
	// We don't check the version number in the premaster secret. For one,
	// by checking it, we would leak information about the validity of the
	// encrypted pre-master secret. Secondly, it provides only a small
	// benefit against a downgrade attack and some implementations send the
	// wrong version anyway. See the discussion at the end of section
	// 7.4.7.1 of RFC 4346.
	return preMasterSecret, nil
}

func (ka rsaKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
	return errors.New("tls: unexpected ServerKeyExchange")
}

func (ka rsaKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, cert *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) {
	preMasterSecret := make([]byte, 48)
	preMasterSecret[0] = byte(clientHello.vers >> 8)
	preMasterSecret[1] = byte(clientHello.vers)
	_, err := io.ReadFull(config.rand(), preMasterSecret[2:])
	if err != nil {
		return nil, nil, err
	}

	rsaKey, ok := cert.PublicKey.(*rsa.PublicKey)
	if !ok {
		return nil, nil, errors.New("tls: server certificate contains incorrect key type for selected ciphersuite")
	}
	encrypted, err := rsa.EncryptPKCS1v15(config.rand(), rsaKey, preMasterSecret)
	if err != nil {
		return nil, nil, err

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Verify the server is conformant — RSA cipher suites must not produce a ServerKeyExchange message.
  2. Test with a reference TLS implementation (openssl s_client) to reproduce and isolate the issue.
  3. Check that the negotiated cipher suite is correct (no downgrade or misconfiguration).
  4. If you control the server, ensure the RSA key agreement path doesn't send ServerKeyExchange.
  5. Switch to ECDHE cipher suites (preferred in modern TLS) to avoid the RSA key exchange path entirely.
Defensive patterns

Strategy: try-catch

Validate before calling

// No caller validation possible — this is a protocol violation by the server.
// Verify cipher suite selection on the client:
func validateNoRSAKeyExchangeWithServerKE(cfg *tls.Config) error {
    // RSA key exchange suites should not be used with servers that send ServerKeyExchange
    // This is informational only; the error surfaces during handshake
    return nil
}

Try / catch

// Client-side: handle during handshake
if err := conn.Handshake(); err != nil {
    if strings.Contains(err.Error(), "unexpected ServerKeyExchange") {
        log.Printf("server sent invalid ServerKeyExchange for RSA suite: %v", err)
    }
}

Prevention

When it happens

Trigger: The client received a ServerKeyExchange message despite the negotiated cipher suite using plain RSA key exchange (not RSA-ECDHE or RSA-DHE). This should never happen with a conformant server. It indicates a protocol violation or a cipher suite negotiation mismatch.

Common situations: A buggy server that sends ServerKeyExchange for an RSA cipher suite; a MITM that injects a spurious ServerKeyExchange; cipher suite negotiation manipulation; a server implementation bug where DHE/ECDHE code path is erroneously taken for RSA suites.

Understand the failure class

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/8a449e200e3f0fa7. Report an issue: GitHub.