golang/go · error

tls: failed to sign handshake: {err}

Error message

tls: failed to sign handshake: {err}

What it means

Thrown during the TLS 1.3 client handshake when signing the CertificateVerify message fails. The client calls crypto.SignMessage with its private key (RSA-PSS, ECDSA, or Ed25519) over the transcript hash, and the underlying signer returned an error. This is a local signing failure, not a network or peer problem.

Source

Thrown at src/crypto/tls/handshake_client_tls13.go:802

		// CertificateRequestInfo supported signature algorithms.
		c.sendAlert(alertHandshakeFailure)
		return err
	}

	sigType, sigHash, err := typeAndHashFromSignatureScheme(certVerifyMsg.signatureAlgorithm)
	if err != nil {
		return c.sendAlert(alertInternalError)
	}

	signed := signedMessage(clientSignatureContext, hs.transcript)
	signOpts := crypto.SignerOpts(sigHash)
	if sigType == signatureRSAPSS {
		signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
	}
	sig, err := crypto.SignMessage(cert.PrivateKey.(crypto.Signer), c.config.rand(), signed, signOpts)
	if err != nil {
		c.sendAlert(alertInternalError)
		return errors.New("tls: failed to sign handshake: " + err.Error())
	}
	certVerifyMsg.signature = sig

	if _, err := hs.c.writeHandshakeRecord(certVerifyMsg, hs.transcript); err != nil {
		return err
	}

	return nil
}

func (hs *clientHandshakeStateTLS13) sendClientFinished() error {
	c := hs.c

	finished := &finishedMsg{
		verifyData: hs.suite.finishedHash(c.out.trafficSecret, hs.transcript),
	}

	if _, err := hs.c.writeHandshakeRecord(finished, hs.transcript); err != nil {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Verify the configured tls.Certificate.PrivateKey implements crypto.Signer (all standard *rsa.PrivateKey, *ecdsa.PrivateKey, and ed25519.PrivateKey values do).
  2. Ensure the private key was loaded with the correct parser (x509.ParsePKCS1PrivateKey / ParsePKCS8PrivateKey / ParseECPrivateKey) and matches the certificate's public key algorithm.
  3. If the key lives in an HSM or PKCS#11 module, check that the token is present, the session is authorized, and the mechanism (RSA-PSS with PSSSaltLengthEqualsHash) is permitted by token policy.
  4. Log the wrapped err (err.Error() is concatenated into the message) to identify whether it is a crypto/rsa, crypto/ecdsa, or signer-type error.

Example fix

// before
cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
if err != nil { return err }
// client.key was an encrypted PEM; PrivateKey came back nil/invalid

// after
decryptPEM, err := decryptBlock(pemBlock, password)
cert, err := tls.X509KeyPair(certPEM, decryptPEM)
if err != nil { return err }
if _, ok := cert.PrivateKey.(crypto.Signer); !ok {
    return errors.New("private key does not implement crypto.Signer")
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before dialing, ensure the client cert key can sign.
if cert, ok := tlsCert.PrivateKey.(crypto.Signer); ok {
    // Optionally dry-run a signature over random bytes with the same
    // opts you expect TLS to use.
    h := sha256.Sum256([]byte("probe"))
    if _, err := cert.Sign(rand.Reader, h[:], crypto.SHA256); err != nil {
        return fmt.Errorf("private key unusable: %w", err)
    }
} else {
    return errors.New("private key does not implement crypto.Signer")
}

Type guard

func isCryptoSigner(k any) bool {
    _, ok := k.(crypto.Signer)
    return ok
}

Try / catch

// Wrap tls.Dial / http.Client.Do and inspect the error string.
conn, err := tls.Dial("tcp", addr, cfg)
if err != nil && strings.Contains(err.Error(), "failed to sign handshake") {
    // surface as a credential/key problem, not a network error
    return fmt.Errorf("client key signing failed: %w", err)
}

Prevention

When it happens

Trigger: clientHandshakeStateTLS13.sendClientCertificateVerify invokes crypto.Signer.Sign via crypto.SignMessage with cert.PrivateKey; failure occurs when the PrivateKey does not implement crypto.Signer, the key is corrupt/unreadable, an HSM/PKCS#11 backing the key rejects the operation, or RSA-PSS salt length / hash parameters are incompatible with the key.

Common situations: Loading a certificate with a private key that omits Sign (e.g. a raw *rsa.PrivateKey wrapped incorrectly), a hardware token yanked mid-handshake, an HSM policy blocking the requested hash, or feeding a key parsed without the matching Parse function so its internal state is incomplete.

Understand the failure class

Related errors


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