crypto/tls: add ML-DSA support

Tested against Google Chrome Canary 150.0.7834.0.

The Client-TLSv10, Client-TLSv11, and Client-TLSv12 recordings need to
change because they are configured with a version range that includes
TLS 1.3 (even if the OpenSSL server selects the lower one), so they
include ML-DSA signature algorithms in the Client Hello. The
Client-TLSv13 and Server-TLSv13-ClientAuthRequested* recordings need to
change because they now advertise ML-DSA support.

Because of #79481, we need to regenerate all recordings anyway, so defer
doing so to the next CL 779662, to avoid churning twice and bloating the
git history.

Fixes #78888

Change-Id: I14e6a89b459166155261aa8fc0edb83c6a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/776709
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Bypass: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
This commit is contained in:
Filippo Valsorda 2026-05-09 14:14:46 +02:00 committed by Gopher Robot
parent 003833a138
commit c74ba7d265
24 changed files with 1060 additions and 61 deletions

View file

@ -6,3 +6,9 @@ pkg crypto/x509, const MLDSA65 = 18 #78888
pkg crypto/x509, const MLDSA65 SignatureAlgorithm #78888
pkg crypto/x509, const MLDSA87 = 19 #78888
pkg crypto/x509, const MLDSA87 SignatureAlgorithm #78888
pkg crypto/tls, const MLDSA44 = 2308 #78888
pkg crypto/tls, const MLDSA44 SignatureScheme #78888
pkg crypto/tls, const MLDSA65 = 2309 #78888
pkg crypto/tls, const MLDSA65 SignatureScheme #78888
pkg crypto/tls, const MLDSA87 = 2310 #78888
pkg crypto/tls, const MLDSA87 SignatureScheme #78888

View file

@ -6,3 +6,6 @@ The new [crypto/mldsa] package implements the post-quantum ML-DSA signature
scheme specified in FIPS 204.
[crypto/x509] now supports ML-DSA private keys, public keys, and signatures.
[crypto/tls] now supports ML-DSA signatures in TLS 1.3, with the new
[MLDSA44], [MLDSA65], and [MLDSA87] [SignatureScheme] values.

View file

@ -0,0 +1 @@
<!-- crypto/tls ML-DSA support is documented in doc/next/6-stdlib/70-mldsa.md. -->

View file

@ -289,9 +289,8 @@ bXVL8iKLrG91IYQByUHZIn3WVAd2bfi4MfKagRt0ggd4
expectNoErr(t, err)
expectNoErr(t, errRet2(kem.NewPrivateKey(kb)))
expectNoErr(t, errRet2(kem.NewPublicKey(k.PublicKey().Bytes())))
if fips140.Version() == "v1.0.0" {
t.Skip("FIPS 140-3 Module v1.0.0 does not provide HPKE GCM modes")
}
// HPKE GCM modes were added in v1.26.0.
cryptotest.MustMinimumFIPS140ModuleVersion(t, "v1.26.0")
c, err := hpke.Seal(k.PublicKey(), hpke.HKDFSHA256(), hpke.AES128GCM(), nil, nil)
expectNoErr(t, err)
_, err = hpke.Open(k, hpke.HKDFSHA256(), hpke.AES128GCM(), nil, c)

View file

@ -10,6 +10,7 @@ import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/mldsa"
"crypto/rsa"
"errors"
"fmt"
@ -45,6 +46,14 @@ func verifyHandshakeSignature(sigType uint8, pubkey crypto.PublicKey, hashFunc c
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:
pubKey, ok := pubkey.(*rsa.PublicKey)
if !ok {
@ -133,6 +142,8 @@ func typeAndHashFromSignatureScheme(signatureAlgorithm SignatureScheme) (sigType
sigType = signatureECDSA
case Ed25519:
sigType = signatureEd25519
case MLDSA44, MLDSA65, MLDSA87:
sigType = signatureMLDSA
default:
return 0, 0, fmt.Errorf("unsupported signature algorithm: %v", signatureAlgorithm)
}
@ -147,6 +158,8 @@ func typeAndHashFromSignatureScheme(signatureAlgorithm SignatureScheme) (sigType
hash = crypto.SHA512
case Ed25519:
hash = directSigning
case MLDSA44, MLDSA65, MLDSA87:
hash = directSigning
default:
return 0, 0, fmt.Errorf("unsupported signature algorithm: %v", signatureAlgorithm)
}
@ -168,6 +181,8 @@ func legacyTypeAndHashFromPublicKey(pub crypto.PublicKey) (sigType uint8, hash c
// full signature, and not even OpenSSL bothers with the
// complexity, so we can't even test it properly.
return 0, 0, fmt.Errorf("tls: Ed25519 public keys are not supported before TLS 1.2")
case *mldsa.PublicKey:
return 0, 0, fmt.Errorf("tls: ML-DSA public keys are not supported before TLS 1.3")
default:
return 0, 0, fmt.Errorf("tls: unsupported public key: %T", pub)
}
@ -224,6 +239,17 @@ func signatureSchemesForPublicKey(version uint16, pub crypto.PublicKey) []Signat
return sigAlgs
case ed25519.PublicKey:
return []SignatureScheme{Ed25519}
case *mldsa.PublicKey:
switch pub.Parameters() {
case mldsa.MLDSA44():
return []SignatureScheme{MLDSA44}
case mldsa.MLDSA65():
return []SignatureScheme{MLDSA65}
case mldsa.MLDSA87():
return []SignatureScheme{MLDSA87}
default:
panic("tls: internal error: unknown ML-DSA parameter set: " + pub.Parameters().String())
}
default:
return nil
}
@ -300,6 +326,8 @@ func unsupportedCertificateError(cert *Certificate) error {
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)
}

View file

@ -6,6 +6,9 @@ package tls
import (
"crypto"
"crypto/fips140"
"crypto/internal/cryptotest"
"crypto/mldsa"
"crypto/tls/internal/fips140tls"
"internal/testenv"
"strconv"
@ -39,6 +42,9 @@ func TestSignatureSelection(t *testing.T) {
{testECDSAP256Cert, []SignatureScheme{ECDSAWithP256AndSHA256}, VersionTLS13, "", ECDSAWithP256AndSHA256, signatureECDSA, crypto.SHA256},
{testEd25519Cert, []SignatureScheme{Ed25519}, VersionTLS12, "", Ed25519, signatureEd25519, directSigning},
{testEd25519Cert, []SignatureScheme{Ed25519}, VersionTLS13, "", Ed25519, signatureEd25519, directSigning},
{testMLDSA44Cert, []SignatureScheme{MLDSA44}, VersionTLS13, "", MLDSA44, signatureMLDSA, directSigning},
{testMLDSA65Cert, []SignatureScheme{MLDSA65}, VersionTLS13, "", MLDSA65, signatureMLDSA, directSigning},
{testMLDSA87Cert, []SignatureScheme{MLDSA87}, VersionTLS13, "", MLDSA87, signatureMLDSA, directSigning},
// TLS 1.2 without signature_algorithms extension
{testRSA2048Cert, nil, VersionTLS12, "tlssha1=1", PKCS1WithSHA1, signaturePKCS1v15, crypto.SHA1},
@ -53,6 +59,10 @@ func TestSignatureSelection(t *testing.T) {
if fips140tls.Required() && test.expectedHash == crypto.SHA1 {
t.Skip("skipping test not compatible with TLS FIPS mode")
}
switch test.expectedSigAlg {
case MLDSA44, MLDSA65, MLDSA87:
cryptotest.MustMinimumFIPS140ModuleVersion(t, "v1.26.0")
}
if test.godebug != "" {
testenv.SetGODEBUG(t, test.godebug)
} else {
@ -115,6 +125,18 @@ func TestSignatureSelection(t *testing.T) {
{testECDSAP256Cert, []SignatureScheme{ECDSAWithSHA1}, VersionTLS12},
{testRSA2048Cert, nil, VersionTLS12},
{testECDSAP256Cert, nil, VersionTLS12},
// ML-DSA requires TLS 1.3.
{testMLDSA44Cert, []SignatureScheme{MLDSA44}, VersionTLS12},
{testMLDSA65Cert, []SignatureScheme{MLDSA65}, VersionTLS12},
{testMLDSA87Cert, []SignatureScheme{MLDSA87}, VersionTLS12},
// ML-DSA parameter sets don't cross-match.
{testMLDSA44Cert, []SignatureScheme{MLDSA65}, VersionTLS13},
{testMLDSA65Cert, []SignatureScheme{MLDSA87}, VersionTLS13},
{testMLDSA87Cert, []SignatureScheme{MLDSA44}, VersionTLS13},
// ML-DSA cert with non-ML-DSA peer sig algs and vice versa.
{testMLDSA44Cert, []SignatureScheme{Ed25519}, VersionTLS13},
{testRSA2048Cert, []SignatureScheme{MLDSA44}, VersionTLS13},
{testECDSAP256Cert, []SignatureScheme{MLDSA44}, VersionTLS13},
}
for testNo, test := range badTests {
@ -153,12 +175,24 @@ func TestLegacyTypeAndHash(t *testing.T) {
if err == nil {
t.Errorf("Ed25519: unexpected success")
}
// ML-DSA is not supported by TLS 1.0 and 1.1. Skip under FIPS 140-3 module
// v1.0.0 which doesn't support ML-DSA public keys.
if fips140.Version() != "v1.0.0" {
for _, key := range []*mldsa.PrivateKey{
testMLDSA44Key, testMLDSA65Key, testMLDSA87Key,
} {
if _, _, err := legacyTypeAndHashFromPublicKey(key.PublicKey()); err == nil {
t.Errorf("%s: unexpected success", key.PublicKey().Parameters())
}
}
}
}
// TestSupportedSignatureAlgorithms checks that all supportedSignatureAlgorithms
// have valid type and hash information.
func TestSupportedSignatureAlgorithms(t *testing.T) {
for _, sigAlg := range supportedSignatureAlgorithms(VersionTLS12) {
for _, sigAlg := range supportedSignatureAlgorithms(VersionTLS12, VersionTLS13) {
sigType, hash, err := typeAndHashFromSignatureScheme(sigAlg)
if err != nil {
t.Errorf("%v: unexpected error: %v", sigAlg, err)
@ -166,7 +200,7 @@ func TestSupportedSignatureAlgorithms(t *testing.T) {
if sigType == 0 {
t.Errorf("%v: missing signature type", sigAlg)
}
if hash == 0 && sigAlg != Ed25519 {
if hash == 0 && sigAlg != Ed25519 && sigAlg != MLDSA44 && sigAlg != MLDSA65 && sigAlg != MLDSA87 {
t.Errorf("%v: missing hash", sigAlg)
}
}

View file

@ -11,7 +11,8 @@ import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/fips140"
icryptotest "crypto/internal/cryptotest"
"crypto/mldsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
@ -32,12 +33,10 @@ var generate = flag.Bool("generate", false, "regenerate certificates_test.go")
func TestGenerateCertificates(t *testing.T) {
testenv.MustHaveSource(t)
icryptotest.MustMinimumFIPS140ModuleVersion(t, "v1.26.0")
if testing.Short() && !*generate {
t.Skip("set -generate to regenerate certificates_test.go, or run without -short to check")
}
if fips140.Version() == "v1.0.0" {
t.Skip("FIPS 140-3 module v1.0.0 doesn't support SetGlobalRandom")
}
// Allow RSA keys below 1024 bits for testRSA512.
testenv.SetGODEBUG(t, "rsa1024min=0")
@ -313,6 +312,78 @@ func TestGenerateCertificates(t *testing.T) {
}
emit("testClientRSAPSS", "RSA 2048 client leaf, SAN=test.golang.example, issued by Client Root.\n\t// Signature algorithm is SHA512WithRSAPSS (rsaEncryption SPKI, rsassaPss signature).", der, clientRSAPSSKey)
// ML-DSA-44.
mldsa44Key, err := mldsa.GenerateKey(mldsa.MLDSA44())
if err != nil {
t.Fatal(err)
}
tmpl = serverLeaf("ML-DSA-44", "test.golang.example")
der, err = x509.CreateCertificate(rand.Reader, tmpl, rootCert, mldsa44Key.PublicKey(), rootKey)
if err != nil {
t.Fatal(err)
}
emit("testMLDSA44", "ML-DSA-44 server leaf, SAN=test.golang.example, issued by Root.", der, mldsa44Key)
// ML-DSA-65.
mldsa65Key, err := mldsa.GenerateKey(mldsa.MLDSA65())
if err != nil {
t.Fatal(err)
}
tmpl = serverLeaf("ML-DSA-65", "test.golang.example")
der, err = x509.CreateCertificate(rand.Reader, tmpl, rootCert, mldsa65Key.PublicKey(), rootKey)
if err != nil {
t.Fatal(err)
}
emit("testMLDSA65", "ML-DSA-65 server leaf, SAN=test.golang.example, issued by Root.", der, mldsa65Key)
// ML-DSA-87.
mldsa87Key, err := mldsa.GenerateKey(mldsa.MLDSA87())
if err != nil {
t.Fatal(err)
}
tmpl = serverLeaf("ML-DSA-87", "test.golang.example")
der, err = x509.CreateCertificate(rand.Reader, tmpl, rootCert, mldsa87Key.PublicKey(), rootKey)
if err != nil {
t.Fatal(err)
}
emit("testMLDSA87", "ML-DSA-87 server leaf, SAN=test.golang.example, issued by Root.", der, mldsa87Key)
// Client ML-DSA-44.
clientMLDSA44Key, err := mldsa.GenerateKey(mldsa.MLDSA44())
if err != nil {
t.Fatal(err)
}
tmpl = clientLeaf("clientAuth ML-DSA-44", "test.golang.example")
der, err = x509.CreateCertificate(rand.Reader, tmpl, clientRootCert, clientMLDSA44Key.PublicKey(), clientRootKey)
if err != nil {
t.Fatal(err)
}
emit("testClientMLDSA44", "ML-DSA-44 client leaf, SAN=test.golang.example, issued by Client Root.", der, clientMLDSA44Key)
// Client ML-DSA-65.
clientMLDSA65Key, err := mldsa.GenerateKey(mldsa.MLDSA65())
if err != nil {
t.Fatal(err)
}
tmpl = clientLeaf("clientAuth ML-DSA-65", "test.golang.example")
der, err = x509.CreateCertificate(rand.Reader, tmpl, clientRootCert, clientMLDSA65Key.PublicKey(), clientRootKey)
if err != nil {
t.Fatal(err)
}
emit("testClientMLDSA65", "ML-DSA-65 client leaf, SAN=test.golang.example, issued by Client Root.", der, clientMLDSA65Key)
// Client ML-DSA-87.
clientMLDSA87Key, err := mldsa.GenerateKey(mldsa.MLDSA87())
if err != nil {
t.Fatal(err)
}
tmpl = clientLeaf("clientAuth ML-DSA-87", "test.golang.example")
der, err = x509.CreateCertificate(rand.Reader, tmpl, clientRootCert, clientMLDSA87Key.PublicKey(), clientRootKey)
if err != nil {
t.Fatal(err)
}
emit("testClientMLDSA87", "ML-DSA-87 client leaf, SAN=test.golang.example, issued by Client Root.", der, clientMLDSA87Key)
// Generate certificates_test.go.
var buf bytes.Buffer
fmt.Fprint(&buf, `// Code generated by certificates_generator_test.go; DO NOT EDIT.
@ -323,8 +394,10 @@ package tls
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/mldsa"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
)
`)
@ -335,8 +408,16 @@ import (
fmt.Fprintf(&buf, "\t// %s\n", p.comment)
fmt.Fprintf(&buf, "\t%sCert = parseTestCert(%sCertPEM, %sKeyPEM)\n",
p.name, p.name, p.name)
fmt.Fprintf(&buf, "\t%sKey = %sCert.PrivateKey.(%s)\n\n",
p.name, p.name, p.keyType)
// ML-DSA is unavailable in FIPS 140-3 module v1.0.0; the cert
// loads with a nil PrivateKey under that module, so use a tolerant
// type assertion. Tests using these vars must skip on v1.0.0.
if p.keyType == "*mldsa.PrivateKey" {
fmt.Fprintf(&buf, "\t%sKey, _ = %sCert.PrivateKey.(%s)\n\n",
p.name, p.name, p.keyType)
} else {
fmt.Fprintf(&buf, "\t%sKey = %sCert.PrivateKey.(%s)\n\n",
p.name, p.name, p.keyType)
}
}
fmt.Fprint(&buf, ` // x509.CertPool containing testRootCert.
testRootCertPool = newTestCertPool(testRootCertPEM)
@ -352,11 +433,28 @@ import (
}
fmt.Fprint(&buf, `func parseTestCert(certPEM, keyPEM string) Certificate {
tlsCert, err := X509KeyPair([]byte(certPEM), []byte(testingKey(keyPEM)))
if err != nil {
panic(err)
var cert Certificate
block, _ := pem.Decode([]byte(certPEM))
if block == nil {
panic("failed to parse certificate PEM")
}
return tlsCert
cert.Certificate = [][]byte{block.Bytes}
cert.Leaf, _ = x509.ParseCertificate(block.Bytes)
if cert.Leaf == nil {
panic("failed to parse certificate")
}
// Don't parse the private key for ML-DSA certificates with FIPS 140-3 module v1.0.0.
if cert.Leaf.PublicKeyAlgorithm != x509.UnknownPublicKeyAlgorithm {
block, _ = pem.Decode([]byte(keyPEM))
if block == nil {
panic("failed to parse key PEM")
}
cert.PrivateKey, _ = x509.ParsePKCS8PrivateKey(block.Bytes)
if cert.PrivateKey == nil {
panic("failed to parse private key")
}
}
return cert
}
func newTestCertPool(certPEM string) *x509.CertPool {

View file

@ -6,8 +6,10 @@ package tls
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/mldsa"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
)
var (
@ -75,6 +77,30 @@ var (
testClientRSAPSSCert = parseTestCert(testClientRSAPSSCertPEM, testClientRSAPSSKeyPEM)
testClientRSAPSSKey = testClientRSAPSSCert.PrivateKey.(*rsa.PrivateKey)
// ML-DSA-44 server leaf, SAN=test.golang.example, issued by Root.
testMLDSA44Cert = parseTestCert(testMLDSA44CertPEM, testMLDSA44KeyPEM)
testMLDSA44Key, _ = testMLDSA44Cert.PrivateKey.(*mldsa.PrivateKey)
// ML-DSA-65 server leaf, SAN=test.golang.example, issued by Root.
testMLDSA65Cert = parseTestCert(testMLDSA65CertPEM, testMLDSA65KeyPEM)
testMLDSA65Key, _ = testMLDSA65Cert.PrivateKey.(*mldsa.PrivateKey)
// ML-DSA-87 server leaf, SAN=test.golang.example, issued by Root.
testMLDSA87Cert = parseTestCert(testMLDSA87CertPEM, testMLDSA87KeyPEM)
testMLDSA87Key, _ = testMLDSA87Cert.PrivateKey.(*mldsa.PrivateKey)
// ML-DSA-44 client leaf, SAN=test.golang.example, issued by Client Root.
testClientMLDSA44Cert = parseTestCert(testClientMLDSA44CertPEM, testClientMLDSA44KeyPEM)
testClientMLDSA44Key, _ = testClientMLDSA44Cert.PrivateKey.(*mldsa.PrivateKey)
// ML-DSA-65 client leaf, SAN=test.golang.example, issued by Client Root.
testClientMLDSA65Cert = parseTestCert(testClientMLDSA65CertPEM, testClientMLDSA65KeyPEM)
testClientMLDSA65Key, _ = testClientMLDSA65Cert.PrivateKey.(*mldsa.PrivateKey)
// ML-DSA-87 client leaf, SAN=test.golang.example, issued by Client Root.
testClientMLDSA87Cert = parseTestCert(testClientMLDSA87CertPEM, testClientMLDSA87KeyPEM)
testClientMLDSA87Key, _ = testClientMLDSA87Cert.PrivateKey.(*mldsa.PrivateKey)
// x509.CertPool containing testRootCert.
testRootCertPool = newTestCertPool(testRootCertPEM)
// x509.CertPool containing testClientRootCert.
@ -621,12 +647,402 @@ jxrfmmrMaIXhnSWAXeBn6Gz+VAgRhfiZpP5/4YS8rPV1d8pOfuQGuP+zDmke1nLC
sZpT/jzJuZsUkbuLREQbZGQ=
-----END TESTING KEY-----`
const testMLDSA44CertPEM = `
-----BEGIN CERTIFICATE-----
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
5oYWLUWYOf4=
-----END CERTIFICATE-----`
const testMLDSA44KeyPEM = `
-----BEGIN TESTING KEY-----
MDQCAQAwCwYJYIZIAWUDBAMRBCKAIKjca7do6h6TzowpEdGGuG0qv0TIYdgnqV2S
Mcvu+n6A
-----END TESTING KEY-----`
const testMLDSA65CertPEM = `
-----BEGIN CERTIFICATE-----
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
3xQD501vTZeBE/2Z+9u5jR78I8z9oijS
-----END CERTIFICATE-----`
const testMLDSA65KeyPEM = `
-----BEGIN TESTING KEY-----
MDQCAQAwCwYJYIZIAWUDBAMSBCKAIEEEYHBN/doadMzAAyaqjdxoQDKDiGb+9tKn
xCTHrLlF
-----END TESTING KEY-----`
const testMLDSA87CertPEM = `
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----`
const testMLDSA87KeyPEM = `
-----BEGIN TESTING KEY-----
MDQCAQAwCwYJYIZIAWUDBAMTBCKAIF21EV0gObGfCvdWN+cM+pRH0lPg1qU2VLip
r0TYx5dY
-----END TESTING KEY-----`
const testClientMLDSA44CertPEM = `
-----BEGIN CERTIFICATE-----
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
TKUtVXxH5czj31ZMuAYfycdf/sdfvprjxcI=
-----END CERTIFICATE-----`
const testClientMLDSA44KeyPEM = `
-----BEGIN TESTING KEY-----
MDQCAQAwCwYJYIZIAWUDBAMRBCKAIHdJoEDI6UeYdZ2U21l0ep3akAB7qA+mLAWX
cufwhk2g
-----END TESTING KEY-----`
const testClientMLDSA65CertPEM = `
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----`
const testClientMLDSA65KeyPEM = `
-----BEGIN TESTING KEY-----
MDQCAQAwCwYJYIZIAWUDBAMSBCKAIOtV+uJia3QCimf1E6NpjLFdNFvp5eQCjjJO
JA04aNQw
-----END TESTING KEY-----`
const testClientMLDSA87CertPEM = `
-----BEGIN CERTIFICATE-----
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
SV/Pw5w+PSi7tg==
-----END CERTIFICATE-----`
const testClientMLDSA87KeyPEM = `
-----BEGIN TESTING KEY-----
MDQCAQAwCwYJYIZIAWUDBAMTBCKAIJOInTmP6L2i2T7bVftiIR5OkrnZTSQLimAK
NFW4io0J
-----END TESTING KEY-----`
func parseTestCert(certPEM, keyPEM string) Certificate {
tlsCert, err := X509KeyPair([]byte(certPEM), []byte(testingKey(keyPEM)))
if err != nil {
panic(err)
var cert Certificate
block, _ := pem.Decode([]byte(certPEM))
if block == nil {
panic("failed to parse certificate PEM")
}
return tlsCert
cert.Certificate = [][]byte{block.Bytes}
cert.Leaf, _ = x509.ParseCertificate(block.Bytes)
if cert.Leaf == nil {
panic("failed to parse certificate")
}
// Don't parse the private key for ML-DSA certificates with FIPS 140-3 module v1.0.0.
if cert.Leaf.PublicKeyAlgorithm != x509.UnknownPublicKeyAlgorithm {
block, _ = pem.Decode([]byte(keyPEM))
if block == nil {
panic("failed to parse key PEM")
}
cert.PrivateKey, _ = x509.ParsePKCS8PrivateKey(block.Bytes)
if cert.PrivateKey == nil {
panic("failed to parse private key")
}
}
return cert
}
func newTestCertPool(certPEM string) *x509.CertPool {

View file

@ -12,6 +12,8 @@ import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/fips140"
"crypto/mldsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
@ -216,11 +218,12 @@ const (
signatureRSAPSS
signatureECDSA
signatureEd25519
signatureMLDSA
)
// directSigning is a standard Hash value that signals that no pre-hashing
// should be performed, and that the input should be signed directly. It is the
// hash function associated with the Ed25519 signature scheme.
// hash function associated with the Ed25519 and ML-DSA signature schemes.
var directSigning crypto.Hash = 0
// helloRetryRequestRandom is set as the Random value of a ServerHello
@ -425,6 +428,11 @@ const (
// EdDSA algorithms.
Ed25519 SignatureScheme = 0x0807
// ML-DSA algorithms.
MLDSA44 SignatureScheme = 0x0904
MLDSA65 SignatureScheme = 0x0905
MLDSA87 SignatureScheme = 0x0906
// Legacy signature and hash algorithms for TLS 1.2.
PKCS1WithSHA1 SignatureScheme = 0x0201
ECDSAWithSHA1 SignatureScheme = 0x0203
@ -1486,6 +1494,9 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error {
return errors.New("connection doesn't support Ed25519")
}
ecdsaCipherSuite = true
case *mldsa.PublicKey:
// ML-DSA requires TLS 1.3, which we already excluded above.
return errors.New("connection doesn't support ML-DSA")
case *rsa.PublicKey:
default:
return supportsRSAFallback(unsupportedCertificateError(c))
@ -1610,8 +1621,8 @@ var writerMutex sync.Mutex
type Certificate struct {
Certificate [][]byte
// PrivateKey contains the private key corresponding to the public key in
// Leaf. This must implement [crypto.Signer] with an RSA, ECDSA or Ed25519
// PublicKey.
// Leaf. This must implement [crypto.Signer] with an RSA, ECDSA, Ed25519
// (TLS 1.2+), or ML-DSA (TLS 1.3) PublicKey.
//
// For a server up to TLS 1.2, it can also implement crypto.Decrypter with
// an RSA PublicKey.
@ -1747,15 +1758,21 @@ func unexpectedMessageError(wanted, got any) error {
var testingOnlySupportedSignatureAlgorithms []SignatureScheme
// supportedSignatureAlgorithms returns the supported signature algorithms for
// the given minimum TLS version, to advertise in ClientHello and
// CertificateRequest messages.
func supportedSignatureAlgorithms(minVers uint16) []SignatureScheme {
// the given range of TLS versions, to advertise in ClientHello and
// CertificateRequest messages. An algorithm is included if it is enabled at any
// version in the range.
func supportedSignatureAlgorithms(minVers, maxVers uint16) []SignatureScheme {
sigAlgs := defaultSupportedSignatureAlgorithms()
if testingOnlySupportedSignatureAlgorithms != nil {
sigAlgs = slices.Clone(testingOnlySupportedSignatureAlgorithms)
}
return slices.DeleteFunc(sigAlgs, func(s SignatureScheme) bool {
return isDisabledSignatureAlgorithm(minVers, s, false)
for v := minVers; v <= maxVers; v++ {
if !isDisabledSignatureAlgorithm(v, s, false) {
return false
}
}
return true
})
}
@ -1766,6 +1783,18 @@ func isDisabledSignatureAlgorithm(version uint16, s SignatureScheme, isCert bool
return true
}
switch s {
case MLDSA44, MLDSA65, MLDSA87:
// ML-DSA is not available in FIPS 140-3 module v1.0.0.
if fips140.Version() == "v1.0.0" {
return true
}
// ML-DSA codepoints are only defined for TLS 1.3.
if version < VersionTLS13 {
return true
}
}
// For the _cert extension we include all algorithms, including SHA-1 and
// PKCS#1 v1.5, because it's more likely that something on our side will be
// willing to accept a *-with-SHA1 certificate (e.g. with a custom
@ -1795,10 +1824,15 @@ func isDisabledSignatureAlgorithm(version uint16, s SignatureScheme, isCert bool
// supportedSignatureAlgorithmsCert returns the supported algorithms for
// signatures in certificates.
func supportedSignatureAlgorithmsCert() []SignatureScheme {
func supportedSignatureAlgorithmsCert(minVers, maxVers uint16) []SignatureScheme {
sigAlgs := defaultSupportedSignatureAlgorithms()
return slices.DeleteFunc(sigAlgs, func(s SignatureScheme) bool {
return isDisabledSignatureAlgorithm(0, s, true)
for v := minVers; v <= maxVers; v++ {
if !isDisabledSignatureAlgorithm(v, s, true) {
return false
}
}
return true
})
}

View file

@ -18,6 +18,9 @@ func _() {
_ = x[ECDSAWithP384AndSHA384-1283]
_ = x[ECDSAWithP521AndSHA512-1539]
_ = x[Ed25519-2055]
_ = x[MLDSA44-2308]
_ = x[MLDSA65-2309]
_ = x[MLDSA87-2310]
_ = x[PKCS1WithSHA1-513]
_ = x[ECDSAWithSHA1-515]
}
@ -32,10 +35,12 @@ const (
_SignatureScheme_name_6 = "PKCS1WithSHA512"
_SignatureScheme_name_7 = "ECDSAWithP521AndSHA512"
_SignatureScheme_name_8 = "PSSWithSHA256PSSWithSHA384PSSWithSHA512Ed25519"
_SignatureScheme_name_9 = "MLDSA44MLDSA65MLDSA87"
)
var (
_SignatureScheme_index_8 = [...]uint8{0, 13, 26, 39, 46}
_SignatureScheme_index_9 = [...]uint8{0, 7, 14, 21}
)
func (i SignatureScheme) String() string {
@ -59,6 +64,9 @@ func (i SignatureScheme) String() string {
case 2052 <= i && i <= 2055:
i -= 2052
return _SignatureScheme_name_8[_SignatureScheme_index_8[i]:_SignatureScheme_index_8[i+1]]
case 2308 <= i && i <= 2310:
i -= 2308
return _SignatureScheme_name_9[_SignatureScheme_index_9[i]:_SignatureScheme_index_9[i+1]]
default:
return "SignatureScheme(" + strconv.FormatInt(int64(i), 10) + ")"
}
@ -117,8 +125,9 @@ const _ClientAuthType_name = "NoClientCertRequestClientCertRequireAnyClientCertV
var _ClientAuthType_index = [...]uint8{0, 12, 29, 49, 72, 98}
func (i ClientAuthType) String() string {
if i < 0 || i >= ClientAuthType(len(_ClientAuthType_index)-1) {
idx := int(i) - 0
if i < 0 || idx >= len(_ClientAuthType_index)-1 {
return "ClientAuthType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _ClientAuthType_name[_ClientAuthType_index[i]:_ClientAuthType_index[i+1]]
return _ClientAuthType_name[_ClientAuthType_index[idx]:_ClientAuthType_index[idx+1]]
}

View file

@ -40,6 +40,9 @@ func defaultCurvePreferences() []CurveID {
// Note that in TLS 1.2, the ECDSA algorithms are not constrained to P-256, etc.
func defaultSupportedSignatureAlgorithms() []SignatureScheme {
return []SignatureScheme{
MLDSA44,
MLDSA65,
MLDSA87,
PSSWithSHA256,
ECDSAWithP256AndSHA256,
Ed25519,

View file

@ -10,6 +10,8 @@ import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/internal/boring"
"crypto/mldsa"
"crypto/rsa"
"crypto/x509"
)
@ -42,6 +44,9 @@ var (
PSSWithSHA256,
ECDSAWithP256AndSHA256,
Ed25519,
MLDSA44,
MLDSA65,
MLDSA87,
PSSWithSHA384,
PSSWithSHA512,
PKCS1WithSHA256,
@ -72,6 +77,9 @@ func isCertificateAllowedFIPS(c *x509.Certificate) bool {
return k.Curve == elliptic.P256() || k.Curve == elliptic.P384() || k.Curve == elliptic.P521()
case ed25519.PublicKey:
return true
case *mldsa.PublicKey:
// Only for the native module.
return !boring.Enabled
default:
return false
}

View file

@ -10,6 +10,7 @@ import (
"crypto/fips140"
"crypto/internal/boring"
"crypto/internal/cryptotest"
"crypto/mldsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
@ -26,7 +27,7 @@ import (
var testConfigFIPS140 = &Config{
Time: testTime,
Certificates: []Certificate{testECDSAP256Cert, testRSAPSSCert, testEd25519Cert},
Certificates: []Certificate{testECDSAP256Cert, testRSAPSSCert, testEd25519Cert, testMLDSA44Cert, testMLDSA65Cert, testMLDSA87Cert},
RootCAs: testRootCertPool,
ServerName: "test.golang.example",
}
@ -181,7 +182,7 @@ func isFIPSSignatureScheme(alg SignatureScheme) bool {
PSSWithSHA384,
PSSWithSHA512:
return true
case Ed25519:
case Ed25519, MLDSA44, MLDSA65, MLDSA87:
// Only for the native module.
return !boring.Enabled
case PKCS1WithSHA1, ECDSAWithSHA1:
@ -283,11 +284,18 @@ func TestFIPSServerSignatureAndHash(t *testing.T) {
for _, sigHash := range defaultSupportedSignatureAlgorithms() {
t.Run(fmt.Sprintf("%v", sigHash), func(t *testing.T) {
isMLDSA := sigHash == MLDSA44 || sigHash == MLDSA65 || sigHash == MLDSA87
if isMLDSA {
cryptotest.MustMinimumFIPS140ModuleVersion(t, "v1.26.0")
}
serverConfig := testConfigFIPS140.Clone()
testingOnlySupportedSignatureAlgorithms = []SignatureScheme{sigHash}
// PKCS#1 v1.5 signature algorithms can't be used standalone in TLS
// 1.3, and the ECDSA ones bind to the curve used.
serverConfig.MaxVersion = VersionTLS12
// 1.3, and the ECDSA ones bind to the curve used. However, ML-DSA
// requires TLS 1.3.
if !isMLDSA {
serverConfig.MaxVersion = VersionTLS12
}
runWithFIPSDisabled(t, func(t *testing.T) {
clientErr, serverErr := fipsHandshake(t, testConfigFIPS140, serverConfig)
@ -398,6 +406,15 @@ func TestFIPSCertAlgs(t *testing.T) {
L1_I := fipsCert(t, "L1_I", fipsECDSAKey(t, elliptic.P384()), I_R1, fipsCertLeaf|fipsCertFIPSOK)
L2_I := fipsCert(t, "L2_I", fipsRSAKey(t, 1024), I_R1, fipsCertLeaf)
var L3_I *fipsCertificate
if fips140.Version() != "v1.0.0" {
// ML-DSA is not implemented by the Go+BoringCrypto FIPS 140 module.
mldsaFlags := fipsCertLeaf | fipsCertFIPSOK
if boring.Enabled {
mldsaFlags = fipsCertLeaf
}
L3_I = fipsCert(t, "L3_I", fipsMLDSAKey(t, mldsa.MLDSA44()), I_R1, mldsaFlags)
}
// client verifying server cert
testServerCert := func(t *testing.T, desc string, pool *x509.CertPool, key any, list [][]byte, ok bool) {
@ -465,11 +482,19 @@ func TestFIPSCertAlgs(t *testing.T) {
runWithFIPSDisabled(t, func(t *testing.T) {
testServerCert(t, "basic", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, true)
testClientCert(t, "basic (client cert)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, true)
if L3_I != nil {
testServerCert(t, "basic ML-DSA", r1pool, L3_I.key, [][]byte{L3_I.der, I_R1.der}, true)
testClientCert(t, "basic ML-DSA (client cert)", r1pool, L3_I.key, [][]byte{L3_I.der, I_R1.der}, true)
}
})
runWithFIPSEnabled(t, func(t *testing.T) {
testServerCert(t, "basic (fips)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, false)
testClientCert(t, "basic (fips, client cert)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, false)
if L3_I != nil {
testServerCert(t, "basic ML-DSA (fips)", r1pool, L3_I.key, [][]byte{L3_I.der, I_R1.der}, L3_I.fipsOK)
testClientCert(t, "basic ML-DSA (fips, client cert)", r1pool, L3_I.key, [][]byte{L3_I.der, I_R1.der}, L3_I.fipsOK)
}
})
if t.Failed() {
@ -567,6 +592,14 @@ func fipsECDSAKey(t *testing.T, curve elliptic.Curve) *ecdsa.PrivateKey {
return k
}
func fipsMLDSAKey(t *testing.T, params mldsa.Parameters) *mldsa.PrivateKey {
k, err := mldsa.GenerateKey(params)
if err != nil {
t.Fatal(err)
}
return k
}
type fipsCertificate struct {
name string
org string
@ -622,6 +655,9 @@ func fipsCert(t *testing.T, name string, key any, parent *fipsCertificate, mode
case *ecdsa.PrivateKey:
pub = &k.PublicKey
desc = "ECDSA-" + k.Curve.Params().Name
case *mldsa.PrivateKey:
pub = k.PublicKey()
desc = k.PublicKey().Parameters().String()
default:
t.Fatalf("invalid key %T", key)
}

View file

@ -13,6 +13,7 @@ import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/mldsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
@ -35,6 +36,7 @@ var (
rsaBits = flag.Int("rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set")
ecdsaCurve = flag.String("ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256 (recommended), P384, P521")
ed25519Key = flag.Bool("ed25519", false, "Generate an Ed25519 key")
mldsaKey = flag.Bool("mldsa", false, "Generate an ML-DSA-44 key")
)
func publicKey(priv any) any {
@ -45,6 +47,8 @@ func publicKey(priv any) any {
return &k.PublicKey
case ed25519.PrivateKey:
return k.Public().(ed25519.PublicKey)
case *mldsa.PrivateKey:
return k.PublicKey()
default:
return nil
}
@ -63,6 +67,8 @@ func main() {
case "":
if *ed25519Key {
_, priv, err = ed25519.GenerateKey(rand.Reader)
} else if *mldsaKey {
priv, err = mldsa.GenerateKey(mldsa.MLDSA44())
} else {
priv, err = rsa.GenerateKey(rand.Reader, *rsaBits)
}
@ -81,8 +87,8 @@ func main() {
log.Fatalf("Failed to generate private key: %v", err)
}
// ECDSA, ED25519 and RSA subject keys should have the DigitalSignature
// KeyUsage bits set in the x509.Certificate template
// ECDSA, ED25519, ML-DSA, and RSA subject keys should have the
// DigitalSignature KeyUsage bits set in the x509.Certificate template
keyUsage := x509.KeyUsageDigitalSignature
// Only RSA subject keys should have the KeyEncipherment KeyUsage bits set. In
// the context of TLS this KeyUsage is particular to RSA key exchange and

View file

@ -12,6 +12,7 @@ import (
"crypto/ed25519"
"crypto/hpke"
"crypto/internal/fips140/tls13"
"crypto/mldsa"
"crypto/rsa"
"crypto/subtle"
"crypto/tls/internal/fips140tls"
@ -119,8 +120,8 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli
}
if maxVersion >= VersionTLS12 {
hello.supportedSignatureAlgorithms = supportedSignatureAlgorithms(minVersion)
hello.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithmsCert()
hello.supportedSignatureAlgorithms = supportedSignatureAlgorithms(minVersion, maxVersion)
hello.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithmsCert(minVersion, maxVersion)
}
var keyShareKeys *keySharePrivateKeys
@ -1170,7 +1171,11 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error {
switch certs[0].PublicKey.(type) {
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
break
case *mldsa.PublicKey:
if c.vers < VersionTLS13 {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: server's certificate uses ML-DSA, which requires TLS 1.3")
}
default:
c.sendAlert(alertUnsupportedCertificate)
return fmt.Errorf("tls: server's certificate contains an unsupported type of public key: %T", certs[0].PublicKey)

View file

@ -653,7 +653,7 @@ func (hs *clientHandshakeStateTLS13) readServerCertificate() error {
// See RFC 8446, Section 4.4.3.
// We don't use hs.hello.supportedSignatureAlgorithms because it might
// include PKCS#1 v1.5 and SHA-1 if the ClientHello also supported TLS 1.2.
if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms(c.vers)) ||
if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms(c.vers, c.vers)) ||
!isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, signatureSchemesForPublicKey(c.vers, c.peerCertificates[0].PublicKey)) {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: certificate used with invalid signature algorithm")

View file

@ -179,10 +179,10 @@ func (*clientHelloMsg) Generate(rand *rand.Rand, size int) reflect.Value {
}
}
if rand.Intn(10) > 5 {
m.supportedSignatureAlgorithms = supportedSignatureAlgorithms(VersionTLS12)
m.supportedSignatureAlgorithms = supportedSignatureAlgorithms(VersionTLS12, VersionTLS13)
}
if rand.Intn(10) > 5 {
m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms(VersionTLS12)
m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms(VersionTLS12, VersionTLS13)
}
for i := 0; i < rand.Intn(5); i++ {
m.alpnProtocols = append(m.alpnProtocols, randomString(rand.Intn(20)+1, rand))
@ -465,10 +465,10 @@ func (*certificateRequestMsgTLS13) Generate(rand *rand.Rand, size int) reflect.V
m.scts = true
}
if rand.Intn(10) > 5 {
m.supportedSignatureAlgorithms = supportedSignatureAlgorithms(VersionTLS12)
m.supportedSignatureAlgorithms = supportedSignatureAlgorithms(VersionTLS12, VersionTLS13)
}
if rand.Intn(10) > 5 {
m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms(VersionTLS12)
m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms(VersionTLS12, VersionTLS13)
}
if rand.Intn(10) > 5 {
m.certificateAuthorities = make([][]byte, 3)

View file

@ -9,6 +9,7 @@ import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/mldsa"
"crypto/rsa"
"crypto/subtle"
"crypto/tls/internal/fips140tls"
@ -310,6 +311,11 @@ func (hs *serverHandshakeState) processClientHello() error {
hs.ecSignOk = true
case *rsa.PublicKey:
hs.rsaSignOk = true
case *mldsa.PublicKey:
// ML-DSA can only be used with TLS 1.3.
c.sendAlert(alertInternalError)
return fmt.Errorf("tls: ML-DSA certificates require TLS 1.3, but client negotiated %s",
VersionName(c.vers))
default:
c.sendAlert(alertInternalError)
return fmt.Errorf("tls: unsupported signing key type (%T)", priv.Public())
@ -659,7 +665,7 @@ func (hs *serverHandshakeState) doFullHandshake() error {
}
if c.vers >= VersionTLS12 {
certReq.hasSignatureAlgorithm = true
certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms(c.vers)
certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms(c.vers, c.vers)
}
// An empty list of certificateAuthorities signals to
@ -1002,6 +1008,11 @@ func (c *Conn) processCertsFromClient(certificate Certificate) error {
if len(certs) > 0 {
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)

View file

@ -832,8 +832,8 @@ func (hs *serverHandshakeStateTLS13) sendServerCertificate() error {
certReq := new(certificateRequestMsgTLS13)
certReq.ocspStapling = true
certReq.scts = true
certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms(c.vers)
certReq.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithmsCert()
certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms(c.vers, c.vers)
certReq.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithmsCert(c.vers, c.vers)
if c.config.ClientCAs != nil {
certReq.certificateAuthorities = c.config.ClientCAs.Subjects()
}
@ -1082,7 +1082,7 @@ func (hs *serverHandshakeStateTLS13) readClientCertificate() error {
// See RFC 8446, Section 4.4.3.
// We don't use certReq.supportedSignatureAlgorithms because it would
// require keeping the certificateRequestMsgTLS13 around in the hs.
if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms(c.vers)) ||
if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms(c.vers, c.vers)) ||
!isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, signatureSchemesForPublicKey(c.vers, c.peerCertificates[0].PublicKey)) {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: client certificate used with invalid signature algorithm")

View file

@ -483,12 +483,21 @@ func testHandshake(t *testing.T, clientConfig, serverConfig *Config) (serverStat
c.Close()
return
}
defer func() { errChan <- nil }()
clientState = cli.ConnectionState()
buf, err := io.ReadAll(cli)
if err != nil {
t.Errorf("failed to call cli.Read: %v", err)
if serverConfig.ClientAuth != NoClientCert && clientState.Version == VersionTLS13 {
// In TLS 1.3, client certificates are sent after the server's
// handshake has completed, and the client only learns about it
// reading the alert after the handshake.
errChan <- fmt.Errorf("client (from Read): %v", err)
c.Close()
return
} else {
t.Errorf("failed to call cli.Read: %v", err)
}
}
defer func() { errChan <- nil }()
if got := string(buf); got != sentinel {
t.Errorf("read %q from TLS connection, but expected %q", got, sentinel)
}

View file

@ -294,6 +294,10 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell
if len(sig) < 2 {
return errServerKeyExchange
}
switch ka.signatureAlgorithm {
case MLDSA44, MLDSA65, MLDSA87:
return errors.New("tls: server selected ML-DSA with TLS version < 1.3")
}
}
sigLen := int(sig[0])<<8 | int(sig[1])
if sigLen+2 != len(sig) {

View file

@ -305,7 +305,7 @@ func TestQUICPostHandshakeClientAuthentication(t *testing.T) {
certReq := new(certificateRequestMsgTLS13)
certReq.ocspStapling = true
certReq.scts = true
certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms(VersionTLS13)
certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms(VersionTLS13, VersionTLS13)
certReqBytes, err := certReq.marshal()
if err != nil {
t.Fatal(err)

View file

@ -28,6 +28,7 @@ import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/mldsa"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
@ -353,6 +354,14 @@ func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
if !priv.Public().(ed25519.PublicKey).Equal(pub) {
return fail(errors.New("tls: private key does not match public key"))
}
case *mldsa.PublicKey:
priv, ok := cert.PrivateKey.(*mldsa.PrivateKey)
if !ok {
return fail(errors.New("tls: private key type does not match public key type"))
}
if !priv.PublicKey().Equal(pub) {
return fail(errors.New("tls: private key does not match public key"))
}
default:
return fail(errors.New("tls: unknown public key algorithm"))
}
@ -369,7 +378,7 @@ func parsePrivateKey(der []byte) (crypto.PrivateKey, error) {
}
if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
switch key := key.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, *mldsa.PrivateKey:
return key, nil
default:
return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping")

View file

@ -13,6 +13,8 @@ import (
"crypto/elliptic"
"crypto/fips140"
"crypto/internal/boring"
"crypto/internal/cryptotest"
"crypto/mldsa"
"crypto/rand"
"crypto/tls/internal/fips140tls"
"crypto/x509"
@ -40,16 +42,18 @@ import (
var testTime = func() time.Time { return time.Unix(1476984729, 0) }
var testConfigServer = &Config{
Time: testTime,
Certificates: []Certificate{testECDSAP256Cert, testRSA2048Cert, testEd25519Cert, testSNICert},
ClientCAs: testClientRootCertPool,
Time: testTime,
Certificates: []Certificate{testECDSAP256Cert, testRSA2048Cert, testEd25519Cert, testSNICert,
testMLDSA44Cert, testMLDSA65Cert, testMLDSA87Cert},
ClientCAs: testClientRootCertPool,
}
var testConfigClient = &Config{
Time: testTime,
Certificates: []Certificate{testClientECDSAP256Cert, testClientRSA2048Cert, testClientEd25519Cert},
RootCAs: testRootCertPool,
ServerName: "test.golang.example",
Time: testTime,
Certificates: []Certificate{testClientECDSAP256Cert, testClientRSA2048Cert, testClientEd25519Cert,
testClientMLDSA44Cert, testClientMLDSA65Cert, testClientMLDSA87Cert},
RootCAs: testRootCertPool,
ServerName: "test.golang.example",
}
func TestX509KeyPair(t *testing.T) {
@ -118,16 +122,25 @@ kohxS/xfFg/TEwRSSws+roJr4JFKpO2t3/be5OdqmQ==
-----END EC TESTING KEY-----
`)
var keyPairTests = []struct {
type test struct {
algo string
cert string
key string
}{
}
var keyPairTests = []test{
{"ECDSA", ecdsaCertPEM, ecdsaKeyPEM},
{"RSA", rsaCertPEM, rsaKeyPEM},
{"RSA-untyped", rsaCertPEM, keyPEM}, // golang.org/issue/4477
}
if fips140.Version() != "v1.0.0" {
keyPairTests = append(keyPairTests,
test{"ML-DSA-44", testMLDSA44CertPEM, testingKey(testMLDSA44KeyPEM)},
test{"ML-DSA-65", testMLDSA65CertPEM, testingKey(testMLDSA65KeyPEM)},
test{"ML-DSA-87", testMLDSA87CertPEM, testingKey(testMLDSA87KeyPEM)},
)
}
t.Parallel()
var pem []byte
for _, test := range keyPairTests {
@ -1571,6 +1584,25 @@ func TestClientHelloInfo_SupportsCertificate(t *testing.T) {
CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
},
}, ""}, // static RSA fallback
{testMLDSA44Cert, &ClientHelloInfo{
SignatureSchemes: []SignatureScheme{MLDSA44},
SupportedVersions: []uint16{VersionTLS13},
}, ""},
{testMLDSA65Cert, &ClientHelloInfo{
SignatureSchemes: []SignatureScheme{MLDSA65},
SupportedVersions: []uint16{VersionTLS13},
}, ""},
{testMLDSA87Cert, &ClientHelloInfo{
SignatureSchemes: []SignatureScheme{MLDSA87},
SupportedVersions: []uint16{VersionTLS13},
}, ""},
{testMLDSA44Cert, &ClientHelloInfo{
CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
SupportedCurves: []CurveID{CurveP256},
SupportedPoints: []uint8{pointFormatUncompressed},
SupportedVersions: []uint16{VersionTLS12},
}, "doesn't support ML-DSA"},
}
for i, tt := range tests {
err := tt.chi.SupportsCertificate(&tt.c)
@ -2185,6 +2217,254 @@ func TestHandshakeMLKEM(t *testing.T) {
}
}
// TestSupportedSignatureAlgorithmsMLDSAGating asserts the spec-mandated
// version-level gating of ML-DSA. Outside the FIPS 140-3 v1.0.0 module:
// ML-DSA MUST NOT appear in the TLS 1.2 advertised list, MUST appear in
// the TLS 1.3 advertised list. Under FIPS 140-3 v1.0.0 (which doesn't include
// ML-DSA), ML-DSA MUST NOT be advertised in either extension.
func TestSupportedSignatureAlgorithmsMLDSAGating(t *testing.T) {
mldsaSchemes := []SignatureScheme{MLDSA44, MLDSA65, MLDSA87}
if fips140.Version() == "v1.0.0" {
fullRange := supportedSignatureAlgorithms(VersionTLS10, VersionTLS13)
certExt := supportedSignatureAlgorithmsCert(VersionTLS10, VersionTLS13)
for _, s := range mldsaSchemes {
if slices.Contains(fullRange, s) {
t.Errorf("supportedSignatureAlgorithms contains %v under FIPS 140-3 v1.0.0", s)
}
if slices.Contains(certExt, s) {
t.Errorf("supportedSignatureAlgorithmsCert contains %v under FIPS 140-3 v1.0.0", s)
}
}
return
}
tls12Only := supportedSignatureAlgorithms(VersionTLS12, VersionTLS12)
tls12OnlyCert := supportedSignatureAlgorithmsCert(VersionTLS12, VersionTLS12)
for _, s := range mldsaSchemes {
if slices.Contains(tls12Only, s) {
t.Errorf("supportedSignatureAlgorithms(TLS12, TLS12) contains %v; ML-DSA must not be advertised in TLS 1.2", s)
}
if slices.Contains(tls12OnlyCert, s) {
t.Errorf("supportedSignatureAlgorithmsCert(TLS12, TLS12) contains %v; ML-DSA must not be advertised in TLS 1.2", s)
}
}
tls13Only := supportedSignatureAlgorithms(VersionTLS13, VersionTLS13)
tls13OnlyCert := supportedSignatureAlgorithmsCert(VersionTLS13, VersionTLS13)
for _, s := range mldsaSchemes {
if !slices.Contains(tls13Only, s) {
t.Errorf("supportedSignatureAlgorithms(TLS13, TLS13) is missing %v", s)
}
if !slices.Contains(tls13OnlyCert, s) {
t.Errorf("supportedSignatureAlgorithmsCert(TLS13, TLS13) is missing %v", s)
}
}
}
func TestHandshakeMLDSA(t *testing.T) {
for _, tt := range []struct {
name string
cert Certificate
client Certificate
}{
{"MLDSA44", testMLDSA44Cert, testClientMLDSA44Cert},
{"MLDSA65", testMLDSA65Cert, testClientMLDSA65Cert},
{"MLDSA87", testMLDSA87Cert, testClientMLDSA87Cert},
} {
t.Run(tt.name+"/ServerAuth", func(t *testing.T) {
t.Parallel()
serverConfig := testConfigServer.Clone()
serverConfig.Certificates = []Certificate{tt.cert}
clientConfig := testConfigClient.Clone()
_, cs, err := testHandshake(t, clientConfig, serverConfig)
if fips140.Version() == "v1.0.0" {
if err == nil {
t.Errorf("ML-DSA handshake unexpectedly succeeded with FIPS 140-3 module v1.0.0")
}
// Loaded certificate has cert bytes but no usable private key.
if len(tt.cert.Certificate) == 0 {
t.Errorf("certificate bytes missing")
}
if tt.cert.PrivateKey != nil {
t.Errorf("PrivateKey = %T, want nil under v1.0.0", tt.cert.PrivateKey)
}
return
}
if err != nil {
t.Fatalf("handshake: %v", err)
}
if _, ok := cs.PeerCertificates[0].PublicKey.(*mldsa.PublicKey); !ok {
t.Errorf("server peer cert public key = %T, want *mldsa.PublicKey",
cs.PeerCertificates[0].PublicKey)
}
})
t.Run(tt.name+"/ClientAuth", func(t *testing.T) {
t.Parallel()
serverConfig := testConfigServer.Clone()
serverConfig.Certificates = []Certificate{testECDSAP256Cert}
serverConfig.ClientAuth = RequireAndVerifyClientCert
clientConfig := testConfigClient.Clone()
clientConfig.Certificates = []Certificate{tt.client}
ss, _, err := testHandshake(t, clientConfig, serverConfig)
if fips140.Version() == "v1.0.0" {
if err == nil {
t.Errorf("ML-DSA handshake unexpectedly succeeded with FIPS 140-3 module v1.0.0")
}
// Loaded certificate has cert bytes but no usable private key.
if len(tt.client.Certificate) == 0 {
t.Errorf("certificate bytes missing")
}
if tt.client.PrivateKey != nil {
t.Errorf("PrivateKey = %T, want nil under v1.0.0", tt.client.PrivateKey)
}
return
}
if err != nil {
t.Fatalf("handshake: %v", err)
}
if _, ok := ss.PeerCertificates[0].PublicKey.(*mldsa.PublicKey); !ok {
t.Errorf("client peer cert public key = %T, want *mldsa.PublicKey",
ss.PeerCertificates[0].PublicKey)
}
})
t.Run(tt.name+"/MutualAuth", func(t *testing.T) {
t.Parallel()
serverConfig := testConfigServer.Clone()
serverConfig.Certificates = []Certificate{tt.cert}
serverConfig.ClientAuth = RequireAndVerifyClientCert
clientConfig := testConfigClient.Clone()
clientConfig.Certificates = []Certificate{tt.client}
ss, cs, err := testHandshake(t, clientConfig, serverConfig)
if fips140.Version() == "v1.0.0" {
if err == nil {
t.Errorf("ML-DSA handshake unexpectedly succeeded with FIPS 140-3 module v1.0.0")
}
return
}
if err != nil {
t.Fatalf("handshake: %v", err)
}
if _, ok := cs.PeerCertificates[0].PublicKey.(*mldsa.PublicKey); !ok {
t.Errorf("client-side peer cert public key = %T, want *mldsa.PublicKey",
cs.PeerCertificates[0].PublicKey)
}
if _, ok := ss.PeerCertificates[0].PublicKey.(*mldsa.PublicKey); !ok {
t.Errorf("server-side peer cert public key = %T, want *mldsa.PublicKey",
ss.PeerCertificates[0].PublicKey)
}
})
for _, v := range []uint16{VersionTLS10, VersionTLS12} {
name := tt.name + "/RejectedVersion/" + VersionName(v)
t.Run(name+"/Server", func(t *testing.T) {
if v == VersionTLS10 {
skipFIPS(t) // TLS 1.0 is not allowed in FIPS 140-3 mode.
}
cryptotest.MustMinimumFIPS140ModuleVersion(t, "v1.26.0")
t.Parallel()
serverConfig := testConfigServer.Clone()
serverConfig.MinVersion = VersionTLS10
serverConfig.Certificates = []Certificate{tt.cert}
serverConfig.MaxVersion = v
clientConfig := testConfigClient.Clone()
clientConfig.MinVersion = VersionTLS10
if _, _, err := testHandshake(t, clientConfig, serverConfig); err == nil {
t.Fatal("expected handshake failure when ML-DSA is the only server cert and the negotiation is not TLS 1.3")
} else if !strings.Contains(err.Error(), "ML-DSA") {
t.Errorf("error message should mention ML-DSA, got %q", err)
}
serverConfig.MaxVersion = 0
clientConfig.MaxVersion = v
if _, _, err := testHandshake(t, clientConfig, serverConfig); err == nil {
t.Fatal("expected handshake failure when ML-DSA is the only server cert and the negotiation is not TLS 1.3")
} else if !strings.Contains(err.Error(), "ML-DSA") {
t.Errorf("error message should mention ML-DSA, got %q", err)
}
})
t.Run(name+"/Client", func(t *testing.T) {
cryptotest.MustMinimumFIPS140ModuleVersion(t, "v1.26.0")
t.Parallel()
serverConfig := testConfigServer.Clone()
serverConfig.MinVersion = VersionTLS10
serverConfig.ClientAuth = RequireAndVerifyClientCert
clientConfig := testConfigClient.Clone()
clientConfig.MinVersion = VersionTLS10
clientConfig.Certificates = []Certificate{tt.client}
clientConfig.MaxVersion = v
if _, _, err := testHandshake(t, clientConfig, serverConfig); err == nil {
t.Fatal("expected handshake failure when ML-DSA is the only client cert and the negotiation is not TLS 1.3")
}
// The error message on the client can't be helpful because we
// don't know if the server requires a certificate until/unless
// the server aborts later in the handshake, by which time we
// lost track of which certificate we didn't offer and why.
clientConfig.MaxVersion = 0
serverConfig.MaxVersion = v
if _, _, err := testHandshake(t, clientConfig, serverConfig); err == nil {
t.Fatal("expected handshake failure when ML-DSA is the only client cert and the negotiation is not TLS 1.3")
}
})
}
t.Run(tt.name+"/CorruptedSignature/Server", func(t *testing.T) {
cryptotest.MustMinimumFIPS140ModuleVersion(t, "v1.26.0")
t.Parallel()
serverConfig := testConfigServer.Clone()
serverConfig.Certificates = []Certificate{{
Certificate: tt.cert.Certificate,
PrivateKey: bitFlippingSigner{tt.cert.PrivateKey.(crypto.Signer)},
}}
clientConfig := testConfigClient.Clone()
_, _, err := testHandshake(t, clientConfig, serverConfig)
if err == nil {
t.Fatal("handshake unexpectedly succeeded with corrupted ML-DSA signature")
}
// The client returns the verification error; the server returns
// "remote error: tls: decrypt_error" reflecting the alert.
if !strings.Contains(err.Error(), "decrypt") &&
!strings.Contains(err.Error(), "ML-DSA verification failure") {
t.Errorf("error = %q; want one mentioning decrypt_error or ML-DSA verification", err)
}
})
t.Run(tt.name+"/CorruptedSignature/Client", func(t *testing.T) {
cryptotest.MustMinimumFIPS140ModuleVersion(t, "v1.26.0")
t.Parallel()
serverConfig := testConfigServer.Clone()
serverConfig.ClientAuth = RequireAndVerifyClientCert
clientConfig := testConfigClient.Clone()
clientConfig.Certificates = []Certificate{{
Certificate: tt.client.Certificate,
PrivateKey: bitFlippingSigner{tt.client.PrivateKey.(crypto.Signer)},
}}
_, _, err := testHandshake(t, clientConfig, serverConfig)
if err == nil {
t.Fatal("handshake unexpectedly succeeded with corrupted ML-DSA signature")
}
// The server returns the verification error; the client returns
// "remote error: tls: decrypt_error" reflecting the alert.
if !strings.Contains(err.Error(), "decrypt") &&
!strings.Contains(err.Error(), "ML-DSA verification failure") {
t.Errorf("error = %q; want one mentioning decrypt_error or ML-DSA verification", err)
}
})
}
}
// bitFlippingSigner wraps a crypto.Signer and flips the last bit of every
// signature it produces, used to test that peers reject invalid signatures.
type bitFlippingSigner struct{ crypto.Signer }
func (s bitFlippingSigner) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
sig, err := s.Signer.Sign(rand, msg, opts)
if err != nil {
return nil, err
}
if len(sig) > 0 {
sig[len(sig)-1] ^= 1
}
return sig, nil
}
func TestX509KeyPairPopulateCertificate(t *testing.T) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {