golang/go · error

tls: ML-DSA certificates require TLS 1.3

Error message

tls: ML-DSA certificates require TLS 1.3

What it means

Thrown by unsupportedCertificateError when the certificate key is *mldsa.PublicKey (post-quantum ML-DSA) but the negotiated TLS version is below 1.3. ML-DSA is only defined for TLS 1.3 signatures; the TLS 1.2 signature-scheme table has no ML-DSA entries, so such a cert cannot be used with older versions.

Source

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

	if !ok {
		return fmt.Errorf("tls: certificate private key (%T) does not implement crypto.Signer",
			cert.PrivateKey)
	}

	switch pub := signer.Public().(type) {
	case *ecdsa.PublicKey:
		switch pub.Curve {
		case elliptic.P256():
		case elliptic.P384():
		case elliptic.P521():
		default:
			return fmt.Errorf("tls: unsupported certificate curve (%s)", pub.Curve.Params().Name)
		}
	case *rsa.PublicKey:
		return fmt.Errorf("tls: certificate RSA key size too small for supported signature algorithms")
	case ed25519.PublicKey:
	case *mldsa.PublicKey:
		return errors.New("tls: ML-DSA certificates require TLS 1.3")
	default:
		return fmt.Errorf("tls: unsupported certificate key (%T)", pub)
	}

	if cert.SupportedSignatureAlgorithms != nil {
		return fmt.Errorf("tls: peer doesn't support the certificate custom signature algorithms")
	}

	return fmt.Errorf("tls: internal error: unsupported key (%T)", cert.PrivateKey)
}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Set MinVersion and MaxVersion to TLS 1.3 on both sides when using ML-DSA certs.
  2. Provide a fallback classical (RSA/ECDSA/Ed25519) certificate for TLS 1.2 peers.
  3. Confirm the peer supports TLS 1.3 and ML-DSA signature schemes before presenting the PQ cert.
  4. Use Config.GetCertificate to choose the ML-DSA cert only for TLS 1.3 ClientHellos.

Example fix

// before
cfg.MinVersion = tls.VersionTLS12 // ML-DSA needs 1.3

// after
cfg.MinVersion = tls.VersionTLS13
cfg.Certificates = []tls.Certificate{mldsaCert}
Defensive patterns

Strategy: validation

Validate before calling

// Only present an ML-DSA cert when TLS 1.3 is possible.
func maybeMLDSA(chi *tls.ClientHelloInfo, mldsaCert, classicCert *tls.Certificate) (*tls.Certificate, error) {
    supports13 := false
    for _, v := range chi.SupportedVersions {
        if v == tls.VersionTLS13 { supports13 = true; break }
    }
    if supports13 { return mldsaCert, nil }
    return classicCert, nil
}

Prevention

When it happens

Trigger: Configuring a certificate with an ML-DSA (module-lattice signature) private key while the connection negotiates TLS 1.2 or earlier. Reached in the unsupportedCertificateError branch for *mldsa.PublicKey.

Common situations: Mixing a post-quantum ML-DSA certificate with a peer/config that caps at TLS 1.2; MinVersion left at default while testing PQ certs; a client that only offers TLS 1.2.

Understand the failure class

Related errors


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