golang/go · error

ECDSA verification failure

Error message

ECDSA verification failure

What it means

Thrown by tls verifyHandshakeSignature when ecdsa.VerifyASN1 returns false for an ECDSA signature over the handshake transcript. The public key type matched (it is *ecdsa.PublicKey), but the signature is mathematically invalid for the given signed data. This is a TLS-level signature check on the peer's signed handshake message.

Source

Thrown at src/crypto/tls/auth.go:39

// verifyHandshakeSignature verifies a signature against unhashed handshake contents.
func verifyHandshakeSignature(sigType uint8, pubkey crypto.PublicKey, hashFunc crypto.Hash, signed, sig []byte) error {
	if hashFunc != directSigning {
		if !hashFunc.Available() {
			return fmt.Errorf("hash function unavailable: %v", hashFunc)
		}
		h := hashFunc.New()
		h.Write(signed)
		signed = h.Sum(nil)
	}
	switch sigType {
	case signatureECDSA:
		pubKey, ok := pubkey.(*ecdsa.PublicKey)
		if !ok {
			return fmt.Errorf("expected an ECDSA public key, got %T", pubkey)
		}
		if !ecdsa.VerifyASN1(pubKey, signed, sig) {
			return errors.New("ECDSA verification failure")
		}
	case signatureEd25519:
		pubKey, ok := pubkey.(ed25519.PublicKey)
		if !ok {
			return fmt.Errorf("expected an Ed25519 public key, got %T", pubkey)
		}
		if !ed25519.Verify(pubKey, signed, sig) {
			return errors.New("Ed25519 verification failure")
		}
	case signatureMLDSA:
		pubKey, ok := pubkey.(*mldsa.PublicKey)
		if !ok {
			return fmt.Errorf("expected an ML-DSA public key, got %T", pubkey)
		}
		if err := mldsa.Verify(pubKey, signed, sig, nil); err != nil {
			return fmt.Errorf("ML-DSA verification failure: %w", err)
		}
	case signaturePKCS1v15:

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Verify the server certificate is valid and unmodified (full chain, correct curve).
  2. Capture the handshake with a TLS tracer to confirm the signed bytes and signature match what the peer sent.
  3. Ensure client and server agree on signature algorithm (curve + hash) per the signature_algorithms extension.
  4. If writing the signer, confirm ecdsa.SignASN1 and VerifyASN1 use the same digest and curve.

Example fix

// before (buggy signer hashes twice)
sig, _ := ecdsa.SignASN1(rand, priv, alreadyHashed)
// peer verifies over the raw transcript -> mismatch

// after
sig, err := ecdsa.SignASN1(rand, priv, transcript)
if err != nil { return err }
// verifier hashes per the negotiated hash; pass the right input
Defensive patterns

Strategy: try-catch

Type guard

func isECDSAPubKey(k any) bool {
    _, ok := k.(*ecdsa.PublicKey)
    return ok
}

Try / catch

// During TLS verification, surface ECDSA failures distinctly.
if _, err := tls.Dial("tcp", addr, cfg); err != nil {
    var verErr *tls.RecordHeaderError
    if strings.Contains(err.Error(), "ECDSA verification failure") {
        log.Printf("ECDSA handshake failed (cert/sig/curve mismatch): %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: During a TLS handshake, the peer signs handshake data with ECDSA and the local side's ecdsa.VerifyASN1(pub, signed, sig) returns false. Reached via signatureECDSA in verifyHandshakeSignature, used by TLS 1.2 and 1.3.

Common situations: Mismatched curve between cert and signature; corrupted signature over the wire; wrong transcript bytes due to a MITM or buggy peer; key reuse across contexts; ASN.1 encoding produced by a non-conformant signer.

Related errors


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