mirror of
https://github.com/XTLS/REALITY.git
synced 2026-10-03 20:38:10 +03:00
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:
+189
-21
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user