golang/go · critical
tls: failed to sign handshake: %s
Error message
tls: failed to sign handshake: %s
What it means
The TLS 1.3 server failed to sign the CertificateVerify message using its certificate's private key via crypto.SignMessage. The wrapping %s includes the underlying signing error. The server has a special case: if the key is RSA and too small for RSA-PSS (bit length < hash size * 2 + 2 bytes), it sends alertHandshakeFailure; otherwise it sends alertInternalError. This is a server-side configuration or hardware problem, not a client issue.
Source
Thrown at src/crypto/tls/handshake_server_tls13.go:882
if err != nil {
return c.sendAlert(alertInternalError)
}
signed := signedMessage(serverSignatureContext, hs.transcript)
signOpts := crypto.SignerOpts(sigHash)
if sigType == signatureRSAPSS {
signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
}
sig, err := crypto.SignMessage(hs.cert.PrivateKey.(crypto.Signer), c.config.rand(), signed, signOpts)
if err != nil {
public := hs.cert.PrivateKey.(crypto.Signer).Public()
if rsaKey, ok := public.(*rsa.PublicKey); ok && sigType == signatureRSAPSS &&
rsaKey.N.BitLen()/8 < sigHash.Size()*2+2 { // key too small for RSA-PSS
c.sendAlert(alertHandshakeFailure)
} else {
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 *serverHandshakeStateTLS13) sendServerFinished() 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
- If RSA: use a certificate with at least 2048-bit keys (RSA-PSS requires key_size_bytes >= hash_size*2 + 2).
- Verify the private key file is valid and not corrupted (openssl rsa -check).
- If using an HSM/PKCS#11: ensure the token is connected, the session is valid, and credentials are provided.
- Switch to an ECDSA (P-256) certificate which avoids RSA-PSS minimum-size issues.
- Check that the private key implements crypto.Signer correctly (custom key types must support Sign with the right options).
Example fix
// before: RSA 1024-bit certificate (too small for TLS 1.3 RSA-PSS)
cert, _ := tls.LoadX509KeyPair("rsa1024.crt", "rsa1024.key")
// after: use at least RSA 2048-bit or switch to ECDSA P-256
cert, _ := tls.LoadX509KeyPair("ecdsa_p256.crt", "ecdsa_p256.key") Defensive patterns
Strategy: validation
Validate before calling
// Verify the private key can sign with the required algorithm before starting the server
func verifySigningCapability(key crypto.Signer, sigAlg SignatureScheme) error {
_, _, err := typeAndHashFromSignatureScheme(sigAlg)
if err != nil {
return err
}
// For RSA, check minimum key size for PSS
if rsaKey, ok := key.Public().(*rsa.PublicKey); ok {
_, sigHash, _ := typeAndHashFromSignatureScheme(sigAlg)
if rsaKey.N.BitLen()/8 < sigHash.Size()*2+2 {
return fmt.Errorf("RSA key too small (%d bits) for RSA-PSS with %v", rsaKey.N.BitLen(), sigHash)
}
}
return nil
} Type guard
// Ensure private key implements crypto.Signer
func isSigner(key any) bool {
_, ok := key.(crypto.Signer)
return ok
} Try / catch
// Server-side: check at startup
err := verifySigningCapability(cert.PrivateKey.(crypto.Signer), preferredSigAlg)
if err != nil {
log.Fatal("server certificate cannot sign handshake: ", err)
}
// Runtime: handle Handshake() error
if err := conn.Handshake(); err != nil {
if strings.Contains(err.Error(), "failed to sign handshake") {
log.Printf("signing failure during handshake: %v", err)
}
} Prevention
- Use RSA keys of at least 2048 bits to ensure compatibility with RSA-PSS in TLS 1.3.
- Validate certificate/key pairs at server startup before accepting connections.
- For HSM-backed keys, verify the signing path works before production.
- Prefer ECDSA P-256 certificates which have no minimum-size issue.
When it happens
Trigger: The server's private key (hs.cert.PrivateKey typed as crypto.Signer) failed to produce a signature. Causes: RSA key too small for RSA-PSS with the negotiated hash; HSM/PKCS#11 token error; corrupted key material; hardware crypto accelerator failure; insufficient permissions on the key store.
Common situations: RSA certificate with a 1024-bit or smaller key used with TLS 1.3 (which requires RSA-PSS, needing larger keys); a PKCS#11 or HSM-backed key that is unreachable or has a session timeout; a cert/key pair loaded from an incorrect file path; key stored in a hardware module that requires a PIN that wasn't provided.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: received empty certificates message
- tls: client didn't send one key share in second ClientHello
- tls: client sent unexpected key share in second ClientHello
- tls: client illegally modified second ClientHello
- tls: invalid client finished hash
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/7dd7b6bad5544079.
Report an issue: GitHub.