golang/go · error

tls: client certificate uses ML-DSA, which requires TLS 1.3

Error message

tls: client certificate uses ML-DSA, which requires TLS 1.3

What it means

The client presented an ML-DSA (post-quantum, FIPS 204) certificate on a connection negotiated below TLS 1.3. ML-DSA is only defined for TLS 1.3 client authentication (hybrid post-quantum signatures require the 1.3 CertificateVerify semantics); older versions cannot use it. The server rejects with illegal_parameter.

Source

Thrown at src/crypto/tls/handshake_server.go:1017

	}

	c.peerCertificates = certs
	c.ocspResponse = certificate.OCSPStaple
	c.scts = certificate.SignedCertificateTimestamps

	if len(certs) > 0 {
		if fips140tls.Required() && !isCertificateAllowedFIPS(certs[0]) {
			c.sendAlert(alertBadCertificate)
			err := errors.New("client's certificate is not allowed in FIPS 140-3 mode")
			return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err}
		}

		switch certs[0].PublicKey.(type) {
		case *ecdsa.PublicKey, *rsa.PublicKey, ed25519.PublicKey:
		case *mldsa.PublicKey:
			if c.vers < VersionTLS13 {
				c.sendAlert(alertIllegalParameter)
				return errors.New("tls: client certificate uses ML-DSA, which requires TLS 1.3")
			}
		default:
			c.sendAlert(alertUnsupportedCertificate)
			return fmt.Errorf("tls: client certificate contains an unsupported public key of type %T", certs[0].PublicKey)
		}
	}

	if c.config.VerifyPeerCertificate != nil {
		if err := c.config.VerifyPeerCertificate(certificates, c.verifiedChains); err != nil {
			c.sendAlert(alertBadCertificate)
			return err
		}
	}

	return nil
}

func clientHelloInfo(ctx context.Context, c *Conn, clientHello *clientHelloMsg) *ClientHelloInfo {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Require TLS 1.3 on both ends — set MinVersion: tls.VersionTLS13 on client and server.
  2. If the server must support older TLS, provide the client with a fallback classical (ECDSA/RSA) certificate for those connections.
  3. Remove middleboxes or proxies that cap the negotiated version below 1.3.
  4. Verify both peers run TLS stacks new enough to recognize ML-DSA.

Example fix

// before: version cap allows downgrade
cfg := &tls.Config{
    MaxVersion: tls.VersionTLS12,
    Certificates: []tls.Certificate{mlDSAOnly},
}

// after: require TLS 1.3 for ML-DSA client certs
cfg := &tls.Config{
    MinVersion: tls.VersionTLS13,
    MaxVersion: tls.VersionTLS13,
    Certificates: []tls.Certificate{mlDSAOnly},
}
Defensive patterns

Strategy: validation

Validate before calling

// Client: gate ML-DSA cert usage on TLS 1.3 negotiation.
if _, isMLDSA := cert.PrivateKey.(*mldsa.PrivateKey); isMLDSA {
    cfg.MinVersion = tls.VersionTLS13
    cfg.MaxVersion = tls.VersionTLS13
}

Try / catch

// Client: catch and fall back to a classical cert for legacy servers.
if err != nil && strings.Contains(err.Error(), "ML-DSA, which requires TLS 1.3") {
    cfg.Certificates = []tls.Certificate{classicalCert}
    // retry
}

Prevention

When it happens

Trigger: In processCertsFromClient, certs[0].PublicKey is *mldsa.PublicKey and c.vers < VersionTLS13. The client offered an ML-DSA cert but the connection downgraded to TLS 1.2 or below.

Common situations: A client configured with a post-quantum ML-DSA certificate connecting to a server that does not support TLS 1.3, or a middlebox forcing a downgrade. Also seen in mixed-version migrations where some servers lack TLS 1.3 support.

Understand the failure class

Related errors


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