golang/go · critical

tls: certificate cannot be used with the selected cipher sui

Error message

tls: certificate cannot be used with the selected cipher suite

What it means

During TLS 1.0–1.2 ECDHE ServerKeyExchange signing (modern signing path using crypto.SignMessage), the server's certificate key type doesn't match what the cipher suite expects. The check (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA fires when an RSA cipher suite is paired with an ECDSA cert, or an ECDSA cipher suite is paired with an RSA cert. The ka.isRSA flag was set during cipher suite selection.

Source

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

	}

	var sig []byte
	if ka.version >= VersionTLS12 {
		ka.signatureAlgorithm, err = selectSignatureScheme(ka.version, cert, clientHello.supportedSignatureAlgorithms)
		if err != nil {
			return nil, err
		}
		sigType, sigHash, err := typeAndHashFromSignatureScheme(ka.signatureAlgorithm)
		if err != nil {
			return nil, err
		}
		if sigHash == crypto.SHA1 {
			tlssha1.Value() // ensure godebug is initialized
			tlssha1.IncNonDefault()
		}
		signed := slices.Concat(clientHello.random, hello.random, serverECDHEParams)
		if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA {
			return nil, errors.New("tls: certificate cannot be used with the selected cipher suite")
		}
		signOpts := crypto.SignerOpts(sigHash)
		if sigType == signatureRSAPSS {
			signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
		}
		sig, err = crypto.SignMessage(priv, config.rand(), signed, signOpts)
		if err != nil {
			return nil, errors.New("tls: failed to sign ECDHE parameters: " + err.Error())
		}
	} else {
		sigType, sigHash, err := legacyTypeAndHashFromPublicKey(priv.Public())
		if err != nil {
			return nil, err
		}
		signed := hashForServerKeyExchange(sigType, clientHello.random, hello.random, serverECDHEParams)
		if (sigType == signaturePKCS1v15) != ka.isRSA {
			return nil, errors.New("tls: certificate cannot be used with the selected cipher suite")
		}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Ensure the certificate key type matches the cipher suite family: RSA certs for ECDHE-RSA suites, ECDSA certs for ECDHE-ECDSA suites.
  2. Provide both RSA and ECDSA certificates in tls.Config.Certificates so Go can auto-select the right one.
  3. Verify tls.Config.CipherSuites only includes suites compatible with the loaded certificate(s).
  4. Use GetCertificate callback to dynamically select the correct cert based on the negotiated cipher suite.
  5. Prefer TLS 1.3 which decouples authentication key type from key exchange.

Example fix

// before: ECDSA cert with RSA-only cipher suites
cfg := &tls.Config{
    Certificates: []tls.Certificate{ecdsaCert},
    CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, // needs RSA!
}
// after: match cipher suites to cert or provide both
cfg := &tls.Config{
    Certificates: []tls.Certificate{ecdsaCert},
    CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Verify cert/cipher-suite compatibility before starting the server
func validateCertCipherSuiteCompat(cert *tls.Certificate, cipherSuites []uint16) error {
    isRSA := false
    switch cert.PrivateKey.(crypto.Signer).Public().(type) {
    case *rsa.PublicKey:
        isRSA = true
    }
    for _, suite := range cipherSuites {
        suiteIsRSA := strings.Contains(cipherSuiteName(suite), "_RSA_") ||
                      (!strings.Contains(cipherSuiteName(suite), "_ECDSA_"))
        // Simplified check — adapt to actual suite classification
        if isRSA != suiteIsRSA && strings.Contains(cipherSuiteName(suite), "ECDHE") {
            // Potential mismatch for ECDHE suites
        }
    }
    return nil
}

Type guard

// Determine if certificate key is RSA for cipher suite matching
func isRSACert(cert *tls.Certificate) bool {
    signer, ok := cert.PrivateKey.(crypto.Signer)
    if !ok {
        return false
    }
    _, isRSA := signer.Public().(*rsa.PublicKey)
    return isRSA
}

Try / catch

// Validate at startup
for _, suite := range cfg.CipherSuites {
    if err := validateCertCipherSuiteCompat(&cert, cfg.CipherSuites); err != nil {
        log.Fatal("cert/cipher-suite mismatch: ", err)
    }
}
// Runtime:
if err := conn.Handshake(); err != nil {
    if strings.Contains(err.Error(), "certificate cannot be used") {
        log.Printf("cert/suite mismatch: %v", err)
    }
}

Prevention

When it happens

Trigger: Server signs ECDHE parameters using ka.signatureAlgorithm. If the negotiated cipher suite is ECDHE-RSA (ka.isRSA=true) but the certificate is ECDSA, or the suite is ECDHE-ECDSA (ka.isRSA=false) but the cert is RSA, this mismatch triggers.

Common situations: Server loaded the wrong certificate type for the negotiated cipher suite; misconfigured tls.Config.Certificates with multiple certs where the wrong one was selected; cipher suite preferences and certificate availability are out of sync; a server update changed cert type without updating cipher suite config.

Understand the failure class

Related errors


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