Skip to content

Commit 4b7f7cd

Browse files
FiloSottilegopherbot
authored andcommitted
crypto/tls: implement X25519MLKEM768
This makes three related changes that work particularly well together and would require significant extra work to do separately: it replaces X25519Kyber768Draft00 with X25519MLKEM768, it makes CurvePreferences ordering crypto/tls-selected, and applies a preference to PQ key exchange methods over key shares (to mitigate downgrades). TestHandshakeServerUnsupportedKeyShare was removed because we are not rejecting unsupported key shares anymore (nor do we select them, and rejecting them actively is a MAY). It would have been nice to keep the test to check we still continue successfully, but testClientHelloFailure is broken in the face of any server-side behavior which requires writing any other messages back to the client, or reading them. Updates #69985 Fixes #69393 Change-Id: I58de76f5b8742a9bd4543fd7907c48e038507b19 Reviewed-on: https://go-review.googlesource.com/c/go/+/630775 Reviewed-by: Roland Shoemaker <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Auto-Submit: Filippo Valsorda <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent dbfd003 commit 4b7f7cd

18 files changed

+186
-206
lines changed

api/next/69985.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pkg crypto/tls, const X25519MLKEM768 = 4588 #69985
2+
pkg crypto/tls, const X25519MLKEM768 CurveID #69985

doc/godebug.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,11 @@ than the
203203
[`Certificate.PolicyIdentifiers`](/pkg/crypto/x509/#Certificate.PolicyIdentifiers)
204204
field by default.
205205

206+
Go 1.24 enabled the post-quantum key exchange mechanism
207+
X25519MLKEM768 by default. The default can be reverted using the
208+
[`tlsmlkem` setting](/pkg/crypto/tls/#Config.CurvePreferences).
209+
Go 1.24 also removed X25519Kyber768Draft00 and the Go 1.23 `tlskyber` setting.
210+
206211
### Go 1.23
207212

208213
Go 1.23 changed the channels created by package time to be unbuffered
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
`crypto/tls` now supports the post-quantum [X25519MLKEM768] key exchange. Support
2+
for the experimental X25519Kyber768Draft00 key exchange has been removed.

src/crypto/tls/bogo_config.json

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,14 @@
3333
"TLS-ECH-Server-EarlyData": "Go does not support early (0-RTT) data",
3434
"TLS-ECH-Server-EarlyDataRejected": "Go does not support early (0-RTT) data",
3535

36-
"CurveTest-Client-Kyber-TLS13": "Temporarily disabled since the curve ID is not exposed and it cannot be correctly configured",
37-
"CurveTest-Server-Kyber-TLS13": "Temporarily disabled since the curve ID is not exposed and it cannot be correctly configured",
36+
"MLKEMKeyShareIncludedSecond": "BoGo wants us to order the key shares based on its preference, but we don't support that",
37+
"MLKEMKeyShareIncludedThird": "BoGo wants us to order the key shares based on its preference, but we don't support that",
38+
"PostQuantumNotEnabledByDefaultInClients": "We do enable it by default!",
39+
"*-Kyber-TLS13": "We don't support Kyber, only ML-KEM (BoGo bug ignoring AllCurves?)",
40+
41+
"SendEmptySessionTicket-TLS13": "https://github.com/golang/go/issues/70513",
42+
43+
"*-SignDefault-*": "TODO, partially it encodes BoringSSL defaults, partially we might be missing some implicit behavior of a missing flag",
3844

3945
"SendV2ClientHello*": "We don't support SSLv2",
4046
"*QUIC*": "No QUIC support",
@@ -238,6 +244,7 @@
238244
23,
239245
24,
240246
25,
241-
29
247+
29,
248+
4588
242249
]
243250
}

src/crypto/tls/bogo_shim_test.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ func TestBogoSuite(t *testing.T) {
420420
if *bogoLocalDir != "" {
421421
bogoDir = *bogoLocalDir
422422
} else {
423-
const boringsslModVer = "v0.0.0-20240523173554-273a920f84e8"
423+
const boringsslModVer = "v0.0.0-20241120195446-5cce3fbd23e1"
424424
bogoDir = cryptotest.FetchModule(t, "boringssl.googlesource.com/boringssl.git", boringsslModVer)
425425
}
426426

@@ -473,11 +473,8 @@ func TestBogoSuite(t *testing.T) {
473473
// are present in the output. They are only checked if -bogo-filter
474474
// was not passed.
475475
assertResults := map[string]string{
476-
// TODO: these tests are temporarily disabled, since we don't expose the
477-
// necessary curve ID, and it's currently not possible to correctly
478-
// configure it.
479-
// "CurveTest-Client-Kyber-TLS13": "PASS",
480-
// "CurveTest-Server-Kyber-TLS13": "PASS",
476+
"CurveTest-Client-MLKEM-TLS13": "PASS",
477+
"CurveTest-Server-MLKEM-TLS13": "PASS",
481478
}
482479

483480
for name, result := range results.Tests {

src/crypto/tls/common.go

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,21 @@ const (
145145
type CurveID uint16
146146

147147
const (
148-
CurveP256 CurveID = 23
149-
CurveP384 CurveID = 24
150-
CurveP521 CurveID = 25
151-
X25519 CurveID = 29
152-
153-
// Experimental codepoint for X25519Kyber768Draft00, specified in
154-
// draft-tls-westerbaan-xyber768d00-03. Not exported, as support might be
155-
// removed in the future.
156-
x25519Kyber768Draft00 CurveID = 0x6399 // X25519Kyber768Draft00
148+
CurveP256 CurveID = 23
149+
CurveP384 CurveID = 24
150+
CurveP521 CurveID = 25
151+
X25519 CurveID = 29
152+
X25519MLKEM768 CurveID = 4588
157153
)
158154

155+
func isTLS13OnlyKeyExchange(curve CurveID) bool {
156+
return curve == X25519MLKEM768
157+
}
158+
159+
func isPQKeyExchange(curve CurveID) bool {
160+
return curve == X25519MLKEM768
161+
}
162+
159163
// TLS 1.3 Key Share. See RFC 8446, Section 4.2.8.
160164
type keyShare struct {
161165
group CurveID
@@ -419,9 +423,12 @@ type ClientHelloInfo struct {
419423
// client is using SNI (see RFC 4366, Section 3.1).
420424
ServerName string
421425

422-
// SupportedCurves lists the elliptic curves supported by the client.
423-
// SupportedCurves is set only if the Supported Elliptic Curves
424-
// Extension is being used (see RFC 4492, Section 5.1.1).
426+
// SupportedCurves lists the key exchange mechanisms supported by the
427+
// client. It was renamed to "supported groups" in TLS 1.3, see RFC 8446,
428+
// Section 4.2.7 and [CurveID].
429+
//
430+
// SupportedCurves may be nil in TLS 1.2 and lower if the Supported Elliptic
431+
// Curves Extension is not being used (see RFC 4492, Section 5.1.1).
425432
SupportedCurves []CurveID
426433

427434
// SupportedPoints lists the point formats supported by the client.
@@ -761,14 +768,15 @@ type Config struct {
761768
// which is currently TLS 1.3.
762769
MaxVersion uint16
763770

764-
// CurvePreferences contains the elliptic curves that will be used in
765-
// an ECDHE handshake, in preference order. If empty, the default will
766-
// be used. The client will use the first preference as the type for
767-
// its key share in TLS 1.3. This may change in the future.
771+
// CurvePreferences contains a set of supported key exchange mechanisms.
772+
// The name refers to elliptic curves for legacy reasons, see [CurveID].
773+
// The order of the list is ignored, and key exchange mechanisms are chosen
774+
// from this list using an internal preference order. If empty, the default
775+
// will be used.
768776
//
769-
// From Go 1.23, the default includes the X25519Kyber768Draft00 hybrid
777+
// From Go 1.24, the default includes the [X25519MLKEM768] hybrid
770778
// post-quantum key exchange. To disable it, set CurvePreferences explicitly
771-
// or use the GODEBUG=tlskyber=0 environment variable.
779+
// or use the GODEBUG=tlsmlkem=0 environment variable.
772780
CurvePreferences []CurveID
773781

774782
// DynamicRecordSizingDisabled disables adaptive sizing of TLS records.
@@ -1176,23 +1184,19 @@ func supportedVersionsFromMax(maxVersion uint16) []uint16 {
11761184

11771185
func (c *Config) curvePreferences(version uint16) []CurveID {
11781186
var curvePreferences []CurveID
1179-
if c != nil && len(c.CurvePreferences) != 0 {
1180-
curvePreferences = slices.Clone(c.CurvePreferences)
1181-
if fips140tls.Required() {
1182-
return slices.DeleteFunc(curvePreferences, func(c CurveID) bool {
1183-
return !slices.Contains(defaultCurvePreferencesFIPS, c)
1184-
})
1185-
}
1186-
} else if fips140tls.Required() {
1187+
if fips140tls.Required() {
11871188
curvePreferences = slices.Clone(defaultCurvePreferencesFIPS)
11881189
} else {
11891190
curvePreferences = defaultCurvePreferences()
11901191
}
1191-
if version < VersionTLS13 {
1192-
return slices.DeleteFunc(curvePreferences, func(c CurveID) bool {
1193-
return c == x25519Kyber768Draft00
1192+
if c != nil && len(c.CurvePreferences) != 0 {
1193+
curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool {
1194+
return !slices.Contains(c.CurvePreferences, x)
11941195
})
11951196
}
1197+
if version < VersionTLS13 {
1198+
curvePreferences = slices.DeleteFunc(curvePreferences, isTLS13OnlyKeyExchange)
1199+
}
11961200
return curvePreferences
11971201
}
11981202

src/crypto/tls/common_string.go

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/crypto/tls/defaults.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ import (
1313
// Defaults are collected in this file to allow distributions to more easily patch
1414
// them to apply local policies.
1515

16-
var tlskyber = godebug.New("tlskyber")
16+
var tlsmlkem = godebug.New("tlsmlkem")
1717

18+
// defaultCurvePreferences is the default set of supported key exchanges, as
19+
// well as the preference order.
1820
func defaultCurvePreferences() []CurveID {
19-
if tlskyber.Value() == "0" {
21+
if tlsmlkem.Value() == "0" {
2022
return []CurveID{X25519, CurveP256, CurveP384, CurveP521}
2123
}
22-
// For now, x25519Kyber768Draft00 must always be followed by X25519.
23-
return []CurveID{x25519Kyber768Draft00, X25519, CurveP256, CurveP384, CurveP521}
24+
return []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521}
2425
}
2526

2627
// defaultSupportedSignatureAlgorithms contains the signature and hash algorithms that

src/crypto/tls/fips_test.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,16 +184,13 @@ func TestFIPSServerCipherSuites(t *testing.T) {
184184

185185
func TestFIPSServerCurves(t *testing.T) {
186186
serverConfig := testConfig.Clone()
187+
serverConfig.CurvePreferences = nil
187188
serverConfig.BuildNameToCertificate()
188189

189190
for _, curveid := range defaultCurvePreferences() {
190191
t.Run(fmt.Sprintf("curve=%d", curveid), func(t *testing.T) {
191192
clientConfig := testConfig.Clone()
192193
clientConfig.CurvePreferences = []CurveID{curveid}
193-
if curveid == x25519Kyber768Draft00 {
194-
// x25519Kyber768Draft00 is not supported standalone.
195-
clientConfig.CurvePreferences = append(clientConfig.CurvePreferences, X25519)
196-
}
197194

198195
runWithFIPSDisabled(t, func(t *testing.T) {
199196
if _, _, err := testHandshake(t, clientConfig, serverConfig); err != nil {

src/crypto/tls/handshake_client.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"internal/godebug"
2525
"io"
2626
"net"
27+
"slices"
2728
"strconv"
2829
"strings"
2930
"time"
@@ -156,7 +157,9 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli
156157
}
157158
curveID := hello.supportedCurves[0]
158159
keyShareKeys = &keySharePrivateKeys{curveID: curveID}
159-
if curveID == x25519Kyber768Draft00 {
160+
// Note that if X25519MLKEM768 is supported, it will be first because
161+
// the preference order is fixed.
162+
if curveID == X25519MLKEM768 {
160163
keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), X25519)
161164
if err != nil {
162165
return nil, nil, nil, err
@@ -165,18 +168,20 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli
165168
if _, err := io.ReadFull(config.rand(), seed); err != nil {
166169
return nil, nil, nil, err
167170
}
168-
keyShareKeys.kyber, err = mlkem.NewDecapsulationKey768(seed)
171+
keyShareKeys.mlkem, err = mlkem.NewDecapsulationKey768(seed)
169172
if err != nil {
170173
return nil, nil, nil, err
171174
}
172-
// For draft-tls-westerbaan-xyber768d00-03, we send both a hybrid
173-
// and a standard X25519 key share, since most servers will only
174-
// support the latter. We reuse the same X25519 ephemeral key for
175-
// both, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2.
175+
mlkemEncapsulationKey := keyShareKeys.mlkem.EncapsulationKey().Bytes()
176+
x25519EphemeralKey := keyShareKeys.ecdhe.PublicKey().Bytes()
176177
hello.keyShares = []keyShare{
177-
{group: x25519Kyber768Draft00, data: append(keyShareKeys.ecdhe.PublicKey().Bytes(),
178-
keyShareKeys.kyber.EncapsulationKey().Bytes()...)},
179-
{group: X25519, data: keyShareKeys.ecdhe.PublicKey().Bytes()},
178+
{group: X25519MLKEM768, data: append(mlkemEncapsulationKey, x25519EphemeralKey...)},
179+
}
180+
// If both X25519MLKEM768 and X25519 are supported, we send both key
181+
// shares (as a fallback) and we reuse the same X25519 ephemeral
182+
// key, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2.
183+
if slices.Contains(hello.supportedCurves, X25519) {
184+
hello.keyShares = append(hello.keyShares, keyShare{group: X25519, data: x25519EphemeralKey})
180185
}
181186
} else {
182187
if _, ok := curveForCurveID(curveID); !ok {
@@ -711,7 +716,7 @@ func (hs *clientHandshakeState) doFullHandshake() error {
711716
if ok {
712717
err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, c.peerCertificates[0], skx)
713718
if err != nil {
714-
c.sendAlert(alertUnexpectedMessage)
719+
c.sendAlert(alertIllegalParameter)
715720
return err
716721
}
717722
if len(skx.key) >= 3 && skx.key[0] == 3 /* named curve */ {

0 commit comments

Comments
 (0)