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>
This commit is contained in:
yuhan6665
2026-09-08 21:33:46 -04:00
parent 4f3d2f8139
commit 2d3ccda21d
9 changed files with 291 additions and 151 deletions
+24 -7
View File
@@ -145,19 +145,31 @@ const (
type CurveID uint16
const (
CurveP256 CurveID = 23
CurveP384 CurveID = 24
CurveP521 CurveID = 25
X25519 CurveID = 29
X25519MLKEM768 CurveID = 4588
CurveP256 CurveID = 23
CurveP384 CurveID = 24
CurveP521 CurveID = 25
X25519 CurveID = 29
X25519MLKEM768 CurveID = 4588
SecP256r1MLKEM768 CurveID = 4587
SecP384r1MLKEM1024 CurveID = 4589
)
func isTLS13OnlyKeyExchange(curve CurveID) bool {
return curve == X25519MLKEM768
switch curve {
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024:
return true
default:
return false
}
}
func isPQKeyExchange(curve CurveID) bool {
return curve == X25519MLKEM768
switch curve {
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024:
return true
default:
return false
}
}
// TLS 1.3 Key Share. See RFC 8446, Section 4.2.8.
@@ -812,6 +824,11 @@ type Config struct {
// From Go 1.24, the default includes the [X25519MLKEM768] hybrid
// post-quantum key exchange. To disable it, set CurvePreferences explicitly
// or use the GODEBUG=tlsmlkem=0 environment variable.
//
// From Go 1.26, the default includes the [SecP256r1MLKEM768] and
// [SecP256r1MLKEM768] hybrid post-quantum key exchanges, too. To disable
// them, set CurvePreferences explicitly or use either the
// GODEBUG=tlsmlkem=0 or the GODEBUG=tlssecpmlkem=0 environment variable.
CurvePreferences []CurveID
// DynamicRecordSizingDisabled disables adaptive sizing of TLS records.
+7 -3
View File
@@ -72,16 +72,19 @@ func _() {
_ = x[CurveP521-25]
_ = x[X25519-29]
_ = x[X25519MLKEM768-4588]
_ = x[SecP256r1MLKEM768-4587]
_ = x[SecP384r1MLKEM1024-4589]
}
const (
_CurveID_name_0 = "CurveP256CurveP384CurveP521"
_CurveID_name_1 = "X25519"
_CurveID_name_2 = "X25519MLKEM768"
_CurveID_name_2 = "SecP256r1MLKEM768X25519MLKEM768SecP384r1MLKEM1024"
)
var (
_CurveID_index_0 = [...]uint8{0, 9, 18, 27}
_CurveID_index_2 = [...]uint8{0, 17, 31, 49}
)
func (i CurveID) String() string {
@@ -91,8 +94,9 @@ 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 == 4588:
return _CurveID_name_2
case 4587 <= i && i <= 4589:
i -= 4587
return _CurveID_name_2[_CurveID_index_2[i]:_CurveID_index_2[i+1]]
default:
return "CurveID(" + strconv.FormatInt(int64(i), 10) + ")"
}
+13 -3
View File
@@ -13,14 +13,24 @@ import (
// them to apply local policies.
//var tlsmlkem = godebug.New("tlsmlkem")
//var tlssecpmlkem = godebug.New("tlssecpmlkem")
// defaultCurvePreferences is the default set of supported key exchanges, as
// well as the preference order.
func defaultCurvePreferences() []CurveID {
if false {
return []CurveID{X25519, CurveP256, CurveP384, CurveP521}
switch {
// // tlsmlkem=0 restores the pre-Go 1.24 default.
// case tlsmlkem.Value() == "0":
// return []CurveID{X25519, CurveP256, CurveP384, CurveP521}
// // tlssecpmlkem=0 restores the pre-Go 1.26 default.
// case tlssecpmlkem.Value() == "0":
// return []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521}
default:
return []CurveID{
X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024,
X25519, CurveP256, CurveP384, CurveP521,
}
}
return []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521}
}
// defaultSupportedSignatureAlgorithms returns the signature and hash algorithms that
+2
View File
@@ -32,6 +32,8 @@ var (
}
allowedCurvePreferencesFIPS = []CurveID{
X25519MLKEM768,
SecP256r1MLKEM768,
SecP384r1MLKEM1024,
CurveP256,
CurveP384,
CurveP521,
+14 -37
View File
@@ -11,7 +11,6 @@ import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/hpke"
"crypto/mlkem"
"crypto/rsa"
"crypto/subtle"
"crypto/x509"
@@ -142,43 +141,21 @@ 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")
}
// 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
// ECDH share.
curveID := hello.supportedCurves[0]
keyShareKeys = &keySharePrivateKeys{curveID: curveID}
// Note that if X25519MLKEM768 is supported, it will be first because
// the preference order is fixed.
if curveID == X25519MLKEM768 {
keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), X25519)
if err != nil {
return nil, nil, nil, err
}
seed := make([]byte, mlkem.SeedSize)
if _, err := io.ReadFull(config.rand(), seed); err != nil {
return nil, nil, nil, err
}
keyShareKeys.mlkem, err = mlkem.NewDecapsulationKey768(seed)
if err != nil {
return nil, nil, nil, err
}
mlkemEncapsulationKey := keyShareKeys.mlkem.EncapsulationKey().Bytes()
x25519EphemeralKey := keyShareKeys.ecdhe.PublicKey().Bytes()
hello.keyShares = []keyShare{
{group: X25519MLKEM768, data: append(mlkemEncapsulationKey, x25519EphemeralKey...)},
}
// If both X25519MLKEM768 and X25519 are supported, we send both key
// shares (as a fallback) and we reuse the same X25519 ephemeral
// key, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2.
if slices.Contains(hello.supportedCurves, X25519) {
hello.keyShares = append(hello.keyShares, keyShare{group: X25519, data: x25519EphemeralKey})
}
} else {
if _, ok := curveForCurveID(curveID); !ok {
return nil, nil, nil, errors.New("tls: CurvePreferences includes unsupported curve")
}
keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), curveID)
if err != nil {
return nil, nil, nil, err
}
hello.keyShares = []keyShare{{group: curveID, data: keyShareKeys.ecdhe.PublicKey().Bytes()}}
ke, err := keyExchangeForCurveID(curveID)
if err != nil {
return nil, nil, nil, errors.New("tls: CurvePreferences includes unsupported curve")
}
keyShareKeys, hello.keyShares, err = ke.keyShares(config.rand())
if err != nil {
return nil, nil, nil, err
}
// Only send the fallback ECDH share if the corresponding CurveID is enabled.
if len(hello.keyShares) == 2 && !slices.Contains(hello.supportedCurves, hello.keyShares[1].group) {
hello.keyShares = hello.keyShares[:1]
}
}
+10 -35
View File
@@ -10,7 +10,6 @@ import (
"crypto"
"crypto/hkdf"
"crypto/hmac"
"crypto/mlkem"
"crypto/rsa"
"crypto/subtle"
"errors"
@@ -320,22 +319,18 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share")
}
// Note: we don't support selecting X25519MLKEM768 in a HRR, because it
// is currently first in preference order, so if it's enabled we'll
// always send a key share for it.
//
// This will have to change once we support multiple hybrid KEMs.
if _, ok := curveForCurveID(curveID); !ok {
ke, err := keyExchangeForCurveID(curveID)
if err != nil {
c.sendAlert(alertInternalError)
return errors.New("tls: CurvePreferences includes unsupported curve")
}
key, err := generateECDHEKey(c.config.rand(), curveID)
hs.keyShareKeys, hello.keyShares, err = ke.keyShares(c.config.rand())
if err != nil {
c.sendAlert(alertInternalError)
return err
}
hs.keyShareKeys = &keySharePrivateKeys{curveID: curveID, ecdhe: key}
hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}}
// Do not send the fallback ECDH key share in a HRR response.
hello.keyShares = hello.keyShares[:1]
}
if len(hello.pskIdentities) > 0 {
@@ -475,36 +470,16 @@ func (hs *clientHandshakeStateTLS13) processServerHello() error {
func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
c := hs.c
ecdhePeerData := hs.serverHello.serverShare.data
if hs.serverHello.serverShare.group == X25519MLKEM768 {
if len(ecdhePeerData) != mlkem.CiphertextSize768+x25519PublicKeySize {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid server X25519MLKEM768 key share")
}
ecdhePeerData = hs.serverHello.serverShare.data[mlkem.CiphertextSize768:]
ke, err := keyExchangeForCurveID(hs.serverHello.serverShare.group)
if err != nil {
c.sendAlert(alertInternalError)
return err
}
peerKey, err := hs.keyShareKeys.ecdhe.Curve().NewPublicKey(ecdhePeerData)
sharedKey, err := ke.clientSharedSecret(hs.keyShareKeys, hs.serverHello.serverShare.data)
if err != nil {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid server key share")
}
sharedKey, err := hs.keyShareKeys.ecdhe.ECDH(peerKey)
if err != nil {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid server key share")
}
if hs.serverHello.serverShare.group == X25519MLKEM768 {
if hs.keyShareKeys.mlkem == nil {
return c.sendAlert(alertInternalError)
}
ciphertext := hs.serverHello.serverShare.data[:mlkem.CiphertextSize768]
mlkemShared, err := hs.keyShareKeys.mlkem.Decapsulate(ciphertext)
if err != nil {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid X25519MLKEM768 server key share")
}
sharedKey = append(mlkemShared, sharedKey...)
}
c.curveID = hs.serverHello.serverShare.group
earlySecret := hs.earlySecret
+3 -42
View File
@@ -340,55 +340,16 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
}
c.curveID = selectedGroup
ecdhGroup := selectedGroup
ecdhData := clientKeyShare.data
if selectedGroup == X25519MLKEM768 {
ecdhGroup = X25519
if len(ecdhData) != mlkem.EncapsulationKeySize768+x25519PublicKeySize {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid X25519MLKEM768 client key share")
}
ecdhData = ecdhData[mlkem.EncapsulationKeySize768:]
}
if _, ok := curveForCurveID(ecdhGroup); !ok {
ke, err := keyExchangeForCurveID(selectedGroup)
if err != nil {
c.sendAlert(alertInternalError)
return errors.New("tls: CurvePreferences includes unsupported curve")
}
key, err := generateECDHEKey(c.config.rand(), ecdhGroup)
if err != nil {
c.sendAlert(alertInternalError)
return err
}
hs.hello.serverShare = keyShare{group: selectedGroup, data: key.PublicKey().Bytes()}
peerKey, err := key.Curve().NewPublicKey(ecdhData)
hs.sharedKey, hs.hello.serverShare, err = ke.serverSharedSecret(c.config.rand(), clientKeyShare.data)
if err != nil {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid client key share")
}
hs.sharedKey, err = key.ECDH(peerKey)
if err != nil {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid client key share")
}
if selectedGroup == X25519MLKEM768 {
k, err := mlkem.NewEncapsulationKey768(clientKeyShare.data[:mlkem.EncapsulationKeySize768])
if err != nil {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid X25519MLKEM768 client key share")
}
mlkemSharedSecret, ciphertext := k.Encapsulate()
// draft-kwiatkowski-tls-ecdhe-mlkem-02, Section 3.1.3: "For
// X25519MLKEM768, the shared secret is the concatenation of the ML-KEM
// shared secret and the X25519 shared secret. The shared secret is 64
// bytes (32 bytes for each part)."
hs.sharedKey = append(mlkemSharedSecret, hs.sharedKey...)
// draft-kwiatkowski-tls-ecdhe-mlkem-02, Section 3.1.2: "When the
// X25519MLKEM768 group is negotiated, the server's key exchange value
// is the concatenation of an ML-KEM ciphertext returned from
// encapsulation to the client's encapsulation key, and the server's
// ephemeral X25519 share."
hs.hello.serverShare.data = append(ciphertext, hs.hello.serverShare.data...)
}
selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil)
if err != nil {
+29 -3
View File
@@ -159,17 +159,17 @@ func hashForServerKeyExchange(sigType uint8, hashFunc crypto.Hash, version uint1
type ecdheKeyAgreement struct {
version uint16
isRSA bool
key *ecdh.PrivateKey
// ckx and preMasterSecret are generated in processServerKeyExchange
// and returned in generateClientKeyExchange.
ckx *clientKeyExchangeMsg
preMasterSecret []byte
// curveID and signatureAlgorithm are set by processServerKeyExchange and
// generateServerKeyExchange.
// curveID, signatureAlgorithm, and key are set by processServerKeyExchange
// and generateServerKeyExchange.
curveID CurveID
signatureAlgorithm SignatureScheme
key *ecdh.PrivateKey
}
func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
@@ -372,3 +372,29 @@ func (ka *ecdheKeyAgreement) generateClientKeyExchange(config *Config, clientHel
return ka.preMasterSecret, ka.ckx, nil
}
// generateECDHEKey returns a PrivateKey that implements Diffie-Hellman
// according to RFC 8446, Section 4.2.8.2.
func generateECDHEKey(rand io.Reader, curveID CurveID) (*ecdh.PrivateKey, error) {
curve, ok := curveForCurveID(curveID)
if !ok {
return nil, errors.New("tls: internal error: unsupported curve")
}
return curve.GenerateKey(rand)
}
func curveForCurveID(id CurveID) (ecdh.Curve, bool) {
switch id {
case X25519:
return ecdh.X25519(), true
case CurveP256:
return ecdh.P256(), true
case CurveP384:
return ecdh.P384(), true
case CurveP521:
return ecdh.P521(), true
default:
return nil, false
}
}
+189 -21
View File
@@ -5,6 +5,7 @@
package reality
import (
"crypto"
"crypto/ecdh"
"crypto/hmac"
"crypto/mlkem"
@@ -51,35 +52,202 @@ func (c *cipherSuiteTLS13) exportKeyingMaterial(s *tls13.MasterSecret, transcrip
}
type keySharePrivateKeys struct {
curveID CurveID
ecdhe *ecdh.PrivateKey
mlkem *mlkem.DecapsulationKey768
ecdhe *ecdh.PrivateKey
mlkem crypto.Decapsulator
}
const x25519PublicKeySize = 32
// A keyExchange implements a TLS 1.3 KEM.
type keyExchange interface {
// keyShares generates one or two key shares.
//
// The first one will match the id, the second (if present) reuses the
// traditional component of the requested hybrid, as allowed by
// draft-ietf-tls-hybrid-design-09, Section 3.2.
keyShares(rand io.Reader) (*keySharePrivateKeys, []keyShare, error)
// generateECDHEKey returns a PrivateKey that implements Diffie-Hellman
// according to RFC 8446, Section 4.2.8.2.
func generateECDHEKey(rand io.Reader, curveID CurveID) (*ecdh.PrivateKey, error) {
curve, ok := curveForCurveID(curveID)
if !ok {
return nil, errors.New("tls: internal error: unsupported curve")
// serverSharedSecret computes the shared secret and the server's key share.
serverSharedSecret(rand io.Reader, clientKeyShare []byte) ([]byte, keyShare, error)
// clientSharedSecret computes the shared secret given the server's key
// share and the keys generated by keyShares.
clientSharedSecret(priv *keySharePrivateKeys, serverKeyShare []byte) ([]byte, error)
}
func keyExchangeForCurveID(id CurveID) (keyExchange, error) {
newMLKEMPrivateKey768 := func(b []byte) (crypto.Decapsulator, error) {
return mlkem.NewDecapsulationKey768(b)
}
newMLKEMPrivateKey1024 := func(b []byte) (crypto.Decapsulator, error) {
return mlkem.NewDecapsulationKey1024(b)
}
newMLKEMPublicKey768 := func(b []byte) (crypto.Encapsulator, error) {
return mlkem.NewEncapsulationKey768(b)
}
newMLKEMPublicKey1024 := func(b []byte) (crypto.Encapsulator, error) {
return mlkem.NewEncapsulationKey1024(b)
}
return curve.GenerateKey(rand)
}
func curveForCurveID(id CurveID) (ecdh.Curve, bool) {
switch id {
case X25519:
return ecdh.X25519(), true
return &ecdhKeyExchange{id, ecdh.X25519()}, nil
case CurveP256:
return ecdh.P256(), true
return &ecdhKeyExchange{id, ecdh.P256()}, nil
case CurveP384:
return ecdh.P384(), true
return &ecdhKeyExchange{id, ecdh.P384()}, nil
case CurveP521:
return ecdh.P521(), true
return &ecdhKeyExchange{id, ecdh.P521()}, nil
case X25519MLKEM768:
return &hybridKeyExchange{id, ecdhKeyExchange{X25519, ecdh.X25519()},
32, mlkem.EncapsulationKeySize768, mlkem.CiphertextSize768,
newMLKEMPrivateKey768, newMLKEMPublicKey768}, nil
case SecP256r1MLKEM768:
return &hybridKeyExchange{id, ecdhKeyExchange{CurveP256, ecdh.P256()},
65, mlkem.EncapsulationKeySize768, mlkem.CiphertextSize768,
newMLKEMPrivateKey768, newMLKEMPublicKey768}, nil
case SecP384r1MLKEM1024:
return &hybridKeyExchange{id, ecdhKeyExchange{CurveP384, ecdh.P384()},
97, mlkem.EncapsulationKeySize1024, mlkem.CiphertextSize1024,
newMLKEMPrivateKey1024, newMLKEMPublicKey1024}, nil
default:
return nil, false
return nil, errors.New("tls: unsupported key exchange")
}
}
}
type ecdhKeyExchange struct {
id CurveID
curve ecdh.Curve
}
func (ke *ecdhKeyExchange) keyShares(rand io.Reader) (*keySharePrivateKeys, []keyShare, error) {
priv, err := ke.curve.GenerateKey(rand)
if err != nil {
return nil, nil, err
}
return &keySharePrivateKeys{ecdhe: priv}, []keyShare{{ke.id, priv.PublicKey().Bytes()}}, nil
}
func (ke *ecdhKeyExchange) serverSharedSecret(rand io.Reader, clientKeyShare []byte) ([]byte, keyShare, error) {
key, err := ke.curve.GenerateKey(rand)
if err != nil {
return nil, keyShare{}, err
}
peerKey, err := ke.curve.NewPublicKey(clientKeyShare)
if err != nil {
return nil, keyShare{}, err
}
sharedKey, err := key.ECDH(peerKey)
if err != nil {
return nil, keyShare{}, err
}
return sharedKey, keyShare{ke.id, key.PublicKey().Bytes()}, nil
}
func (ke *ecdhKeyExchange) clientSharedSecret(priv *keySharePrivateKeys, serverKeyShare []byte) ([]byte, error) {
peerKey, err := ke.curve.NewPublicKey(serverKeyShare)
if err != nil {
return nil, err
}
sharedKey, err := priv.ecdhe.ECDH(peerKey)
if err != nil {
return nil, err
}
return sharedKey, nil
}
type hybridKeyExchange struct {
id CurveID
ecdh ecdhKeyExchange
ecdhElementSize int
mlkemPublicKeySize int
mlkemCiphertextSize int
newMLKEMPrivateKey func([]byte) (crypto.Decapsulator, error)
newMLKEMPublicKey func([]byte) (crypto.Encapsulator, error)
}
func (ke *hybridKeyExchange) keyShares(rand io.Reader) (*keySharePrivateKeys, []keyShare, error) {
priv, ecdhShares, err := ke.ecdh.keyShares(rand)
if err != nil {
return nil, nil, err
}
seed := make([]byte, mlkem.SeedSize)
if _, err := io.ReadFull(rand, seed); err != nil {
return nil, nil, err
}
priv.mlkem, err = ke.newMLKEMPrivateKey(seed)
if err != nil {
return nil, nil, err
}
var shareData []byte
// For X25519MLKEM768, the ML-KEM-768 encapsulation key comes first.
// For SecP256r1MLKEM768 and SecP384r1MLKEM1024, the ECDH share comes first.
// See draft-ietf-tls-ecdhe-mlkem-02, Section 4.1.
if ke.id == X25519MLKEM768 {
shareData = append(priv.mlkem.Encapsulator().Bytes(), ecdhShares[0].data...)
} else {
shareData = append(ecdhShares[0].data, priv.mlkem.Encapsulator().Bytes()...)
}
return priv, []keyShare{{ke.id, shareData}, ecdhShares[0]}, nil
}
func (ke *hybridKeyExchange) serverSharedSecret(rand io.Reader, clientKeyShare []byte) ([]byte, keyShare, error) {
if len(clientKeyShare) != ke.ecdhElementSize+ke.mlkemPublicKeySize {
return nil, keyShare{}, errors.New("tls: invalid client key share length for hybrid key exchange")
}
var ecdhShareData, mlkemShareData []byte
if ke.id == X25519MLKEM768 {
mlkemShareData = clientKeyShare[:ke.mlkemPublicKeySize]
ecdhShareData = clientKeyShare[ke.mlkemPublicKeySize:]
} else {
ecdhShareData = clientKeyShare[:ke.ecdhElementSize]
mlkemShareData = clientKeyShare[ke.ecdhElementSize:]
}
ecdhSharedSecret, ks, err := ke.ecdh.serverSharedSecret(rand, ecdhShareData)
if err != nil {
return nil, keyShare{}, err
}
mlkemPeerKey, err := ke.newMLKEMPublicKey(mlkemShareData)
if err != nil {
return nil, keyShare{}, err
}
mlkemSharedSecret, mlkemKeyShare := mlkemPeerKey.Encapsulate()
var sharedKey []byte
if ke.id == X25519MLKEM768 {
sharedKey = append(mlkemSharedSecret, ecdhSharedSecret...)
ks.data = append(mlkemKeyShare, ks.data...)
} else {
sharedKey = append(ecdhSharedSecret, mlkemSharedSecret...)
ks.data = append(ks.data, mlkemKeyShare...)
}
ks.group = ke.id
return sharedKey, ks, nil
}
func (ke *hybridKeyExchange) clientSharedSecret(priv *keySharePrivateKeys, serverKeyShare []byte) ([]byte, error) {
if len(serverKeyShare) != ke.ecdhElementSize+ke.mlkemCiphertextSize {
return nil, errors.New("tls: invalid server key share length for hybrid key exchange")
}
var ecdhShareData, mlkemShareData []byte
if ke.id == X25519MLKEM768 {
mlkemShareData = serverKeyShare[:ke.mlkemCiphertextSize]
ecdhShareData = serverKeyShare[ke.mlkemCiphertextSize:]
} else {
ecdhShareData = serverKeyShare[:ke.ecdhElementSize]
mlkemShareData = serverKeyShare[ke.ecdhElementSize:]
}
ecdhSharedSecret, err := ke.ecdh.clientSharedSecret(priv, ecdhShareData)
if err != nil {
return nil, err
}
mlkemSharedSecret, err := priv.mlkem.Decapsulate(mlkemShareData)
if err != nil {
return nil, err
}
var sharedKey []byte
if ke.id == X25519MLKEM768 {
sharedKey = append(mlkemSharedSecret, ecdhSharedSecret...)
} else {
sharedKey = append(ecdhSharedSecret, mlkemSharedSecret...)
}
return sharedKey, nil
}