diff --git a/auth.go b/auth.go index f7c27d5..99f8aaa 100644 --- a/auth.go +++ b/auth.go @@ -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) } diff --git a/common.go b/common.go index 94dfd66..67c6994 100644 --- a/common.go +++ b/common.go @@ -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 @@ -1520,6 +1528,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)) @@ -1644,8 +1655,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. @@ -1781,15 +1792,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 }) } @@ -1800,6 +1817,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 @@ -1829,10 +1858,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 }) } diff --git a/common_string.go b/common_string.go index 6525e8e..bab3b84 100644 --- a/common_string.go +++ b/common_string.go @@ -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]] } diff --git a/defaults.go b/defaults.go index bf24450..5c03801 100644 --- a/defaults.go +++ b/defaults.go @@ -39,6 +39,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, diff --git a/defaults_fips140.go b/defaults_fips140.go index 2777a79..63bebd0 100644 --- a/defaults_fips140.go +++ b/defaults_fips140.go @@ -10,6 +10,7 @@ import ( "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" + "crypto/mldsa" "crypto/rsa" "crypto/x509" ) @@ -42,6 +43,9 @@ var ( PSSWithSHA256, ECDSAWithP256AndSHA256, Ed25519, + MLDSA44, + MLDSA65, + MLDSA87, PSSWithSHA384, PSSWithSHA512, PKCS1WithSHA256, @@ -72,6 +76,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 true //!boring.Enabled default: return false } diff --git a/generate_cert.go b/generate_cert.go index 14351bb..d6f6e41 100644 --- a/generate_cert.go +++ b/generate_cert.go @@ -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 diff --git a/handshake_client.go b/handshake_client.go index c05b646..22aed23 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -11,6 +11,7 @@ import ( "crypto/ecdsa" "crypto/ed25519" "crypto/hpke" + "crypto/mldsa" "crypto/rsa" "crypto/subtle" "crypto/x509" @@ -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 @@ -1160,6 +1161,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) diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index b30f992..0084dc7 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -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") diff --git a/handshake_server.go b/handshake_server.go index 0079fd5..422cdb1 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -9,6 +9,7 @@ import ( "crypto" "crypto/ecdsa" "crypto/ed25519" + "crypto/mldsa" "crypto/rsa" "crypto/subtle" "crypto/x509" @@ -306,6 +307,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()) @@ -646,7 +652,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 @@ -985,6 +991,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) diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 0e1a0c5..673843f 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -934,8 +934,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() } @@ -1184,7 +1184,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") diff --git a/key_agreement.go b/key_agreement.go index 238597f..220df9a 100644 --- a/key_agreement.go +++ b/key_agreement.go @@ -290,6 +290,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) { diff --git a/tls.go b/tls.go index 20cddba..0d82c72 100644 --- a/tls.go +++ b/tls.go @@ -33,6 +33,7 @@ import ( "crypto/cipher" "crypto/ecdsa" "crypto/ed25519" + "crypto/mldsa" "crypto/mlkem" "crypto/rsa" "crypto/sha256" @@ -817,6 +818,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")) } @@ -833,7 +842,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")