crypto/tls: implement MLKEM1024 key exchange

Fixes #78543

Change-Id: I26a70a64665c75e5116b83f73a75093f6a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/777221
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
This commit is contained in:
Filippo Valsorda 2026-05-12 12:53:42 -04:00 committed by Gopher Robot
parent 97a57b481f
commit 6b0243ccf6
13 changed files with 117 additions and 27 deletions

2
api/next/78543.txt Normal file
View file

@ -0,0 +1,2 @@
pkg crypto/tls, const MLKEM1024 = 514 #78543
pkg crypto/tls, const MLKEM1024 CurveID #78543

View file

@ -0,0 +1,2 @@
The [MLKEM1024] key exchange is now supported. It can be enabled by adding it to
[Config.CurvePreferences].

View file

@ -34,7 +34,12 @@
"TLS-ECH-Server-EarlyDataRejected": "Go does not support early (0-RTT) data",
"MLKEMKeyShareIncludedSecond": "BoGo wants us to order the key shares based on its preference, but we don't support that",
"MLKEMKeyShareIncludedSecond-*": "BoGo wants us to order the key shares based on its preference, but we don't support that",
"MLKEMKeyShareIncludedThird": "BoGo wants us to order the key shares based on its preference, but we don't support that",
"MLKEMKeyShareIncludedThird-*": "BoGo wants us to order the key shares based on its preference, but we don't support that",
"TwoMLKEMs": "BoGo wants us to order the key shares based on its preference, but we don't support that",
"NotJustMLKEMKeyShare-MLKEM1024": "BoringSSL sends an ECC key share for fallback when the main key share is MLKEM1024, we currently don't",
"PostQuantumNotEnabledByDefaultInClients": "We do enable it by default!",
"*-Kyber-TLS13": "We don't support Kyber, only ML-KEM (BoGo bug ignoring AllCurves?)",
@ -76,21 +81,6 @@
"PAKE-Extension-*": "We don't support PAKE",
"*TicketFlags": "We don't support draft-ietf-tls-tlsflags",
"BothMLKEMAndKyber-MLKEM1024": "We don't support ML-KEM 1024 KEX",
"CurveTest-Client-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX",
"CurveTest-Invalid-MLKEMEncapKeyNotReduced-Server-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX",
"CurveTest-Invalid-PadKeyShare-Client-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX",
"CurveTest-Invalid-PadKeyShare-Server-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX",
"CurveTest-Invalid-TruncateKeyShare-Client-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX",
"CurveTest-Invalid-TruncateKeyShare-Server-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX",
"CurveTest-Server-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX",
"JustConfiguringMLKEMWorks-MLKEM1024": "We don't support ML-KEM 1024 KEX",
"NotJustMLKEMKeyShare-MLKEM1024": "We don't support ML-KEM 1024 KEX",
"TwoMLKEMs": "We don't support ML-KEM 1024 KEX",
"MLKEMKeyShareIncludedSecond-MLKEM1024": "We don't support ML-KEM 1024 KEX",
"MLKEMKeyShareIncludedSecond-X25519MLKEM768": "We don't support ML-KEM 1024 KEX",
"MLKEMKeyShareIncludedThird-MLKEM1024": "We don't support ML-KEM 1024 KEX",
"MLKEMKeyShareIncludedThird-X25519MLKEM768": "We don't return key shares in client preference order",
"ECDSAKeyUsage-*": "We don't enforce ECDSA KU",

View file

@ -625,6 +625,8 @@ func TestBogoSuite(t *testing.T) {
assertResults := map[string]string{
"CurveTest-Client-X25519MLKEM768-TLS13": "PASS",
"CurveTest-Server-X25519MLKEM768-TLS13": "PASS",
"CurveTest-Client-MLKEM1024-TLS13": "PASS",
"CurveTest-Server-MLKEM1024-TLS13": "PASS",
// Various signature algorithm tests checking that we enforce our
// preferences on the peer.

View file

@ -155,11 +155,12 @@ const (
X25519MLKEM768 CurveID = 4588
SecP256r1MLKEM768 CurveID = 4587
SecP384r1MLKEM1024 CurveID = 4589
MLKEM1024 CurveID = 514
)
func isTLS13OnlyKeyExchange(curve CurveID) bool {
switch curve {
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024:
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, MLKEM1024:
return true
default:
return false
@ -168,7 +169,7 @@ func isTLS13OnlyKeyExchange(curve CurveID) bool {
func isPQKeyExchange(curve CurveID) bool {
switch curve {
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024:
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, MLKEM1024:
return true
default:
return false

View file

@ -82,17 +82,19 @@ func _() {
_ = x[X25519MLKEM768-4588]
_ = x[SecP256r1MLKEM768-4587]
_ = x[SecP384r1MLKEM1024-4589]
_ = x[MLKEM1024-514]
}
const (
_CurveID_name_0 = "CurveP256CurveP384CurveP521"
_CurveID_name_1 = "X25519"
_CurveID_name_2 = "SecP256r1MLKEM768X25519MLKEM768SecP384r1MLKEM1024"
_CurveID_name_2 = "MLKEM1024"
_CurveID_name_3 = "SecP256r1MLKEM768X25519MLKEM768SecP384r1MLKEM1024"
)
var (
_CurveID_index_0 = [...]uint8{0, 9, 18, 27}
_CurveID_index_2 = [...]uint8{0, 17, 31, 49}
_CurveID_index_3 = [...]uint8{0, 17, 31, 49}
)
func (i CurveID) String() string {
@ -102,9 +104,11 @@ func (i CurveID) String() string {
return _CurveID_name_0[_CurveID_index_0[i]:_CurveID_index_0[i+1]]
case i == 29:
return _CurveID_name_1
case i == 514:
return _CurveID_name_2
case 4587 <= i && i <= 4589:
i -= 4587
return _CurveID_name_2[_CurveID_index_2[i]:_CurveID_index_2[i+1]]
return _CurveID_name_3[_CurveID_index_3[i]:_CurveID_index_3[i+1]]
default:
return "CurveID(" + strconv.FormatInt(int64(i), 10) + ")"
}

View file

@ -37,7 +37,7 @@ func defaultCurveEnabled(c CurveID) bool {
// include every supported key exchange.
func curvePreferenceOrder() []CurveID {
return []CurveID{
X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024,
X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, MLKEM1024,
X25519, CurveP256, CurveP384, CurveP521,
}
}

View file

@ -36,6 +36,7 @@ var (
X25519MLKEM768,
SecP256r1MLKEM768,
SecP384r1MLKEM1024,
MLKEM1024,
CurveP256,
CurveP384,
CurveP521,

View file

@ -151,7 +151,7 @@ func isFIPSCurve(id CurveID) bool {
switch id {
case CurveP256, CurveP384, CurveP521:
return true
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024:
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, MLKEM1024:
// Only for the native module.
return !boring.Enabled
case X25519:

View file

@ -140,7 +140,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli
}
if len(hello.supportedCurves) == 0 {
return nil, nil, nil, errors.New("tls: no supported elliptic curves for ECDHE")
return nil, nil, nil, errors.New("tls: no supported key exchange methods (CurveIDs)")
}
// Since the order is fixed, the first one is always the one to send a
// key share for. All the PQ hybrids sort first, and produce a fallback

View file

@ -54,7 +54,8 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
}
// Consistency check on the presence of a keyShare and its parameters.
if hs.keyShareKeys == nil || hs.keyShareKeys.ecdhe == nil || len(hs.hello.keyShares) == 0 {
if hs.keyShareKeys == nil || (hs.keyShareKeys.ecdhe == nil && hs.keyShareKeys.mlkem == nil) ||
len(hs.hello.keyShares) == 0 {
return c.sendAlert(alertInternalError)
}

View file

@ -107,11 +107,40 @@ func keyExchangeForCurveID(id CurveID) (keyExchange, error) {
return &hybridKeyExchange{id, ecdhKeyExchange{CurveP384, ecdh.P384()},
97, mlkem.EncapsulationKeySize1024, mlkem.CiphertextSize1024,
mlkemGenerateKey1024, mlkemNewPublicKey1024}, nil
case MLKEM1024:
return &mlkem1024KeyExchange{}, nil
default:
return nil, errors.New("tls: unsupported key exchange")
}
}
type mlkem1024KeyExchange struct{}
func (ke *mlkem1024KeyExchange) keyShares(_ io.Reader) (*keySharePrivateKeys, []keyShare, error) {
priv, err := mlkem.GenerateKey1024()
if err != nil {
return nil, nil, err
}
return &keySharePrivateKeys{mlkem: priv}, []keyShare{{MLKEM1024, priv.EncapsulationKey().Bytes()}}, nil
}
func (ke *mlkem1024KeyExchange) serverSharedSecret(_ io.Reader, clientKeyShare []byte) ([]byte, keyShare, error) {
peerKey, err := mlkem.NewEncapsulationKey1024(clientKeyShare)
if err != nil {
return nil, keyShare{}, err
}
sharedKey, keyShareData := peerKey.Encapsulate()
return sharedKey, keyShare{MLKEM1024, keyShareData}, nil
}
func (ke *mlkem1024KeyExchange) clientSharedSecret(priv *keySharePrivateKeys, serverKeyShare []byte) ([]byte, error) {
sharedKey, err := priv.mlkem.Decapsulate(serverKeyShare)
if err != nil {
return nil, err
}
return sharedKey, nil
}
type ecdhKeyExchange struct {
id CurveID
curve ecdh.Curve

View file

@ -2108,6 +2108,18 @@ func TestHandshakeMLKEM(t *testing.T) {
expectClient: []CurveID{SecP256r1MLKEM768, CurveP256},
expectSelected: CurveP256,
},
{
name: "CurveP384HRR",
clientConfig: func(config *Config) {
config.CurvePreferences = []CurveID{SecP256r1MLKEM768, CurveP384}
},
serverConfig: func(config *Config) {
config.CurvePreferences = []CurveID{CurveP384}
},
expectClient: []CurveID{SecP256r1MLKEM768, CurveP384},
expectSelected: CurveP384,
expectHRR: true,
},
{
name: "ClientMLKEMOnly",
clientConfig: func(config *Config) {
@ -2163,14 +2175,60 @@ func TestHandshakeMLKEM(t *testing.T) {
testenv.SetGODEBUG(t, "tlssecpmlkem=0")
},
clientConfig: func(config *Config) {
config.CurvePreferences = []CurveID{CurveP256, SecP256r1MLKEM768}
config.CurvePreferences = []CurveID{CurveP256, SecP256r1MLKEM768, MLKEM1024}
},
serverConfig: func(config *Config) {
config.CurvePreferences = []CurveID{CurveP256, SecP256r1MLKEM768}
config.CurvePreferences = []CurveID{CurveP256, SecP256r1MLKEM768, MLKEM1024}
},
expectClient: []CurveID{SecP256r1MLKEM768, CurveP256},
expectClient: []CurveID{SecP256r1MLKEM768, MLKEM1024, CurveP256},
expectSelected: SecP256r1MLKEM768,
},
{
name: "ClientMLKEM1024Only",
clientConfig: func(config *Config) {
config.CurvePreferences = []CurveID{MLKEM1024}
},
serverConfig: func(config *Config) {
config.CurvePreferences = append(defaultWithPQ, MLKEM1024)
},
expectClient: []CurveID{MLKEM1024},
expectSelected: MLKEM1024,
},
{
name: "ServerMLKEM1024Only",
clientConfig: func(config *Config) {
config.CurvePreferences = append(defaultWithPQ, MLKEM1024)
},
serverConfig: func(config *Config) {
config.CurvePreferences = []CurveID{MLKEM1024}
},
expectClient: []CurveID{X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024,
MLKEM1024, X25519, CurveP256, CurveP384, CurveP521},
expectSelected: MLKEM1024,
expectHRR: true,
},
{
name: "MLKEM1024NotPreferredOverHybrid",
clientConfig: func(config *Config) {
config.CurvePreferences = []CurveID{MLKEM1024, X25519MLKEM768}
},
serverConfig: func(config *Config) {
config.CurvePreferences = []CurveID{MLKEM1024, X25519MLKEM768}
},
expectClient: []CurveID{X25519MLKEM768, MLKEM1024},
expectSelected: X25519MLKEM768,
},
{
name: "MLKEM1024PreferredOverECC",
clientConfig: func(config *Config) {
config.CurvePreferences = []CurveID{X25519, MLKEM1024}
},
serverConfig: func(config *Config) {
config.CurvePreferences = []CurveID{X25519, MLKEM1024}
},
expectClient: []CurveID{MLKEM1024, X25519},
expectSelected: MLKEM1024,
},
}
baseServerConfig := testConfigServer.Clone()