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
- Require TLS 1.3 on both ends — set MinVersion: tls.VersionTLS13 on client and server.
- If the server must support older TLS, provide the client with a fallback classical (ECDSA/RSA) certificate for those connections.
- Remove middleboxes or proxies that cap the negotiated version below 1.3.
- 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
- Require TLS 1.3 when configuring ML-DSA client certificates.
- Provide a classical fallback cert for legacy-server interoperability.
- Remove version-capping middleboxes when deploying post-quantum certs.
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
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: server's certificate uses ML-DSA, which requires TLS 1.
- tls: Encrypted Client Hello cannot be used pre-TLS 1.3
- tls: ML-DSA certificates require TLS 1.3
- tls: client sent encrypted_client_hello extension with unsup
- tls: client sent encrypted_client_hello extension but did no
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/49df159452d93733.
Report an issue: GitHub.