Skip to content

Commit 1768cb4

Browse files
FiloSottilegopherbot
authored andcommitted
crypto/tls: add SecP256r1/SecP384r1MLKEM1024 hybrid post-quantum key exchanges
Fixes #71206 Change-Id: If3cf75261c56828b87ae6805bd2913f56a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/722140 Auto-Submit: Filippo Valsorda <filippo@golang.org> Reviewed-by: Cherry Mui <cherryyz@google.com> Reviewed-by: Roland Shoemaker <roland@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent a909306 commit 1768cb4

File tree

16 files changed

+402
-198
lines changed

16 files changed

+402
-198
lines changed

api/next/71206.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pkg crypto/tls, const SecP256r1MLKEM768 = 4587 #71206
2+
pkg crypto/tls, const SecP256r1MLKEM768 CurveID #71206
3+
pkg crypto/tls, const SecP384r1MLKEM1024 = 4589 #71206
4+
pkg crypto/tls, const SecP384r1MLKEM1024 CurveID #71206

doc/godebug.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ allows malformed hostnames containing colons outside of a bracketed IPv6 address
168168
The default `urlstrictcolons=1` rejects URLs such as `http://localhost:1:2` or `http://::1/`.
169169
Colons are permitted as part of a bracketed IPv6 address, such as `http://[::1]/`.
170170

171+
Go 1.26 enabled two additional post-quantum key exchange mechanisms:
172+
SecP256r1MLKEM768 and SecP384r1MLKEM1024. The default can be reverted using the
173+
[`tlssecpmlkem` setting](/pkg/crypto/tls/#Config.CurvePreferences).
174+
171175
Go 1.26 added a new `tracebacklabels` setting that controls the inclusion of
172176
goroutine labels set through the the `runtime/pprof` package. Setting `tracebacklabels=1`
173177
includes these key/value pairs in the goroutine status header of runtime
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The hybrid [SecP256r1MLKEM768] and [SecP384r1MLKEM1024] post-quantum key
2+
exchanges are now enabled by default. They can be disabled by setting
3+
[Config.CurvePreferences] or with the `tlssecpmlkem=0` GODEBUG setting.

src/crypto/tls/bogo_config.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,9 @@
219219
24,
220220
25,
221221
29,
222-
4588
222+
4587,
223+
4588,
224+
4589
223225
],
224226
"ErrorMap": {
225227
":ECH_REJECTED:": ["tls: server rejected ECH"]

src/crypto/tls/common.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,19 +145,31 @@ 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-
X25519MLKEM768 CurveID = 4588
148+
CurveP256 CurveID = 23
149+
CurveP384 CurveID = 24
150+
CurveP521 CurveID = 25
151+
X25519 CurveID = 29
152+
X25519MLKEM768 CurveID = 4588
153+
SecP256r1MLKEM768 CurveID = 4587
154+
SecP384r1MLKEM1024 CurveID = 4589
153155
)
154156

155157
func isTLS13OnlyKeyExchange(curve CurveID) bool {
156-
return curve == X25519MLKEM768
158+
switch curve {
159+
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024:
160+
return true
161+
default:
162+
return false
163+
}
157164
}
158165

159166
func isPQKeyExchange(curve CurveID) bool {
160-
return curve == X25519MLKEM768
167+
switch curve {
168+
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024:
169+
return true
170+
default:
171+
return false
172+
}
161173
}
162174

163175
// TLS 1.3 Key Share. See RFC 8446, Section 4.2.8.
@@ -787,6 +799,11 @@ type Config struct {
787799
// From Go 1.24, the default includes the [X25519MLKEM768] hybrid
788800
// post-quantum key exchange. To disable it, set CurvePreferences explicitly
789801
// or use the GODEBUG=tlsmlkem=0 environment variable.
802+
//
803+
// From Go 1.26, the default includes the [SecP256r1MLKEM768] and
804+
// [SecP256r1MLKEM768] hybrid post-quantum key exchanges, too. To disable
805+
// them, set CurvePreferences explicitly or use either the
806+
// GODEBUG=tlsmlkem=0 or the GODEBUG=tlssecpmlkem=0 environment variable.
790807
CurvePreferences []CurveID
791808

792809
// DynamicRecordSizingDisabled disables adaptive sizing of TLS records.

src/crypto/tls/common_string.go

Lines changed: 7 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: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,24 @@ import (
1414
// them to apply local policies.
1515

1616
var tlsmlkem = godebug.New("tlsmlkem")
17+
var tlssecpmlkem = godebug.New("tlssecpmlkem")
1718

1819
// defaultCurvePreferences is the default set of supported key exchanges, as
1920
// well as the preference order.
2021
func defaultCurvePreferences() []CurveID {
21-
if tlsmlkem.Value() == "0" {
22+
switch {
23+
// tlsmlkem=0 restores the pre-Go 1.24 default.
24+
case tlsmlkem.Value() == "0":
2225
return []CurveID{X25519, CurveP256, CurveP384, CurveP521}
26+
// tlssecpmlkem=0 restores the pre-Go 1.26 default.
27+
case tlssecpmlkem.Value() == "0":
28+
return []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521}
29+
default:
30+
return []CurveID{
31+
X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024,
32+
X25519, CurveP256, CurveP384, CurveP521,
33+
}
2334
}
24-
return []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521}
2535
}
2636

2737
// defaultSupportedSignatureAlgorithms returns the signature and hash algorithms that

src/crypto/tls/defaults_fips140.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ var (
3232
}
3333
allowedCurvePreferencesFIPS = []CurveID{
3434
X25519MLKEM768,
35+
SecP256r1MLKEM768,
36+
SecP384r1MLKEM1024,
3537
CurveP256,
3638
CurveP384,
3739
CurveP521,

src/crypto/tls/fips140_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,15 @@ func isTLS13CipherSuite(id uint16) bool {
4343
}
4444

4545
func generateKeyShare(group CurveID) keyShare {
46-
key, err := generateECDHEKey(rand.Reader, group)
46+
ke, err := keyExchangeForCurveID(group)
4747
if err != nil {
4848
panic(err)
4949
}
50-
return keyShare{group: group, data: key.PublicKey().Bytes()}
50+
_, shares, err := ke.keyShares(rand.Reader)
51+
if err != nil {
52+
panic(err)
53+
}
54+
return shares[0]
5155
}
5256

5357
func TestFIPSServerProtocolVersion(t *testing.T) {
@@ -132,7 +136,7 @@ func isFIPSCurve(id CurveID) bool {
132136
switch id {
133137
case CurveP256, CurveP384, CurveP521:
134138
return true
135-
case X25519MLKEM768:
139+
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024:
136140
// Only for the native module.
137141
return !boring.Enabled
138142
case X25519:

src/crypto/tls/handshake_client.go

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"crypto/ecdsa"
1212
"crypto/ed25519"
1313
"crypto/hpke"
14-
"crypto/internal/fips140/mlkem"
1514
"crypto/internal/fips140/tls13"
1615
"crypto/rsa"
1716
"crypto/subtle"
@@ -142,43 +141,21 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli
142141
if len(hello.supportedCurves) == 0 {
143142
return nil, nil, nil, errors.New("tls: no supported elliptic curves for ECDHE")
144143
}
144+
// Since the order is fixed, the first one is always the one to send a
145+
// key share for. All the PQ hybrids sort first, and produce a fallback
146+
// ECDH share.
145147
curveID := hello.supportedCurves[0]
146-
keyShareKeys = &keySharePrivateKeys{curveID: curveID}
147-
// Note that if X25519MLKEM768 is supported, it will be first because
148-
// the preference order is fixed.
149-
if curveID == X25519MLKEM768 {
150-
keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), X25519)
151-
if err != nil {
152-
return nil, nil, nil, err
153-
}
154-
seed := make([]byte, mlkem.SeedSize)
155-
if _, err := io.ReadFull(config.rand(), seed); err != nil {
156-
return nil, nil, nil, err
157-
}
158-
keyShareKeys.mlkem, err = mlkem.NewDecapsulationKey768(seed)
159-
if err != nil {
160-
return nil, nil, nil, err
161-
}
162-
mlkemEncapsulationKey := keyShareKeys.mlkem.EncapsulationKey().Bytes()
163-
x25519EphemeralKey := keyShareKeys.ecdhe.PublicKey().Bytes()
164-
hello.keyShares = []keyShare{
165-
{group: X25519MLKEM768, data: append(mlkemEncapsulationKey, x25519EphemeralKey...)},
166-
}
167-
// If both X25519MLKEM768 and X25519 are supported, we send both key
168-
// shares (as a fallback) and we reuse the same X25519 ephemeral
169-
// key, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2.
170-
if slices.Contains(hello.supportedCurves, X25519) {
171-
hello.keyShares = append(hello.keyShares, keyShare{group: X25519, data: x25519EphemeralKey})
172-
}
173-
} else {
174-
if _, ok := curveForCurveID(curveID); !ok {
175-
return nil, nil, nil, errors.New("tls: CurvePreferences includes unsupported curve")
176-
}
177-
keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), curveID)
178-
if err != nil {
179-
return nil, nil, nil, err
180-
}
181-
hello.keyShares = []keyShare{{group: curveID, data: keyShareKeys.ecdhe.PublicKey().Bytes()}}
148+
ke, err := keyExchangeForCurveID(curveID)
149+
if err != nil {
150+
return nil, nil, nil, errors.New("tls: CurvePreferences includes unsupported curve")
151+
}
152+
keyShareKeys, hello.keyShares, err = ke.keyShares(config.rand())
153+
if err != nil {
154+
return nil, nil, nil, err
155+
}
156+
// Only send the fallback ECDH share if the corresponding CurveID is enabled.
157+
if len(hello.keyShares) == 2 && !slices.Contains(hello.supportedCurves, hello.keyShares[1].group) {
158+
hello.keyShares = hello.keyShares[:1]
182159
}
183160
}
184161

0 commit comments

Comments
 (0)