mirror of
https://github.com/XTLS/REALITY.git
synced 2026-10-10 16:28:12 +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:
@@ -145,19 +145,31 @@ const (
|
|||||||
type CurveID uint16
|
type CurveID uint16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CurveP256 CurveID = 23
|
CurveP256 CurveID = 23
|
||||||
CurveP384 CurveID = 24
|
CurveP384 CurveID = 24
|
||||||
CurveP521 CurveID = 25
|
CurveP521 CurveID = 25
|
||||||
X25519 CurveID = 29
|
X25519 CurveID = 29
|
||||||
X25519MLKEM768 CurveID = 4588
|
X25519MLKEM768 CurveID = 4588
|
||||||
|
SecP256r1MLKEM768 CurveID = 4587
|
||||||
|
SecP384r1MLKEM1024 CurveID = 4589
|
||||||
)
|
)
|
||||||
|
|
||||||
func isTLS13OnlyKeyExchange(curve CurveID) bool {
|
func isTLS13OnlyKeyExchange(curve CurveID) bool {
|
||||||
return curve == X25519MLKEM768
|
switch curve {
|
||||||
|
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPQKeyExchange(curve CurveID) bool {
|
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.
|
// 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
|
// From Go 1.24, the default includes the [X25519MLKEM768] hybrid
|
||||||
// post-quantum key exchange. To disable it, set CurvePreferences explicitly
|
// post-quantum key exchange. To disable it, set CurvePreferences explicitly
|
||||||
// or use the GODEBUG=tlsmlkem=0 environment variable.
|
// 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
|
CurvePreferences []CurveID
|
||||||
|
|
||||||
// DynamicRecordSizingDisabled disables adaptive sizing of TLS records.
|
// DynamicRecordSizingDisabled disables adaptive sizing of TLS records.
|
||||||
|
|||||||
+7
-3
@@ -72,16 +72,19 @@ func _() {
|
|||||||
_ = x[CurveP521-25]
|
_ = x[CurveP521-25]
|
||||||
_ = x[X25519-29]
|
_ = x[X25519-29]
|
||||||
_ = x[X25519MLKEM768-4588]
|
_ = x[X25519MLKEM768-4588]
|
||||||
|
_ = x[SecP256r1MLKEM768-4587]
|
||||||
|
_ = x[SecP384r1MLKEM1024-4589]
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_CurveID_name_0 = "CurveP256CurveP384CurveP521"
|
_CurveID_name_0 = "CurveP256CurveP384CurveP521"
|
||||||
_CurveID_name_1 = "X25519"
|
_CurveID_name_1 = "X25519"
|
||||||
_CurveID_name_2 = "X25519MLKEM768"
|
_CurveID_name_2 = "SecP256r1MLKEM768X25519MLKEM768SecP384r1MLKEM1024"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_CurveID_index_0 = [...]uint8{0, 9, 18, 27}
|
_CurveID_index_0 = [...]uint8{0, 9, 18, 27}
|
||||||
|
_CurveID_index_2 = [...]uint8{0, 17, 31, 49}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (i CurveID) String() string {
|
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]]
|
return _CurveID_name_0[_CurveID_index_0[i]:_CurveID_index_0[i+1]]
|
||||||
case i == 29:
|
case i == 29:
|
||||||
return _CurveID_name_1
|
return _CurveID_name_1
|
||||||
case i == 4588:
|
case 4587 <= i && i <= 4589:
|
||||||
return _CurveID_name_2
|
i -= 4587
|
||||||
|
return _CurveID_name_2[_CurveID_index_2[i]:_CurveID_index_2[i+1]]
|
||||||
default:
|
default:
|
||||||
return "CurveID(" + strconv.FormatInt(int64(i), 10) + ")"
|
return "CurveID(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-3
@@ -13,14 +13,24 @@ import (
|
|||||||
// them to apply local policies.
|
// them to apply local policies.
|
||||||
|
|
||||||
//var tlsmlkem = godebug.New("tlsmlkem")
|
//var tlsmlkem = godebug.New("tlsmlkem")
|
||||||
|
//var tlssecpmlkem = godebug.New("tlssecpmlkem")
|
||||||
|
|
||||||
// defaultCurvePreferences is the default set of supported key exchanges, as
|
// defaultCurvePreferences is the default set of supported key exchanges, as
|
||||||
// well as the preference order.
|
// well as the preference order.
|
||||||
func defaultCurvePreferences() []CurveID {
|
func defaultCurvePreferences() []CurveID {
|
||||||
if false {
|
switch {
|
||||||
return []CurveID{X25519, CurveP256, CurveP384, CurveP521}
|
// // 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
|
// defaultSupportedSignatureAlgorithms returns the signature and hash algorithms that
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ var (
|
|||||||
}
|
}
|
||||||
allowedCurvePreferencesFIPS = []CurveID{
|
allowedCurvePreferencesFIPS = []CurveID{
|
||||||
X25519MLKEM768,
|
X25519MLKEM768,
|
||||||
|
SecP256r1MLKEM768,
|
||||||
|
SecP384r1MLKEM1024,
|
||||||
CurveP256,
|
CurveP256,
|
||||||
CurveP384,
|
CurveP384,
|
||||||
CurveP521,
|
CurveP521,
|
||||||
|
|||||||
+14
-37
@@ -11,7 +11,6 @@ import (
|
|||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/hpke"
|
"crypto/hpke"
|
||||||
"crypto/mlkem"
|
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
@@ -142,43 +141,21 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli
|
|||||||
if len(hello.supportedCurves) == 0 {
|
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 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]
|
curveID := hello.supportedCurves[0]
|
||||||
keyShareKeys = &keySharePrivateKeys{curveID: curveID}
|
ke, err := keyExchangeForCurveID(curveID)
|
||||||
// Note that if X25519MLKEM768 is supported, it will be first because
|
if err != nil {
|
||||||
// the preference order is fixed.
|
return nil, nil, nil, errors.New("tls: CurvePreferences includes unsupported curve")
|
||||||
if curveID == X25519MLKEM768 {
|
}
|
||||||
keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), X25519)
|
keyShareKeys, hello.keyShares, err = ke.keyShares(config.rand())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
seed := make([]byte, mlkem.SeedSize)
|
// Only send the fallback ECDH share if the corresponding CurveID is enabled.
|
||||||
if _, err := io.ReadFull(config.rand(), seed); err != nil {
|
if len(hello.keyShares) == 2 && !slices.Contains(hello.supportedCurves, hello.keyShares[1].group) {
|
||||||
return nil, nil, nil, err
|
hello.keyShares = hello.keyShares[:1]
|
||||||
}
|
|
||||||
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()}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-35
@@ -10,7 +10,6 @@ import (
|
|||||||
"crypto"
|
"crypto"
|
||||||
"crypto/hkdf"
|
"crypto/hkdf"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/mlkem"
|
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -320,22 +319,18 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
|
|||||||
c.sendAlert(alertIllegalParameter)
|
c.sendAlert(alertIllegalParameter)
|
||||||
return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share")
|
return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share")
|
||||||
}
|
}
|
||||||
// Note: we don't support selecting X25519MLKEM768 in a HRR, because it
|
ke, err := keyExchangeForCurveID(curveID)
|
||||||
// is currently first in preference order, so if it's enabled we'll
|
if err != nil {
|
||||||
// always send a key share for it.
|
|
||||||
//
|
|
||||||
// This will have to change once we support multiple hybrid KEMs.
|
|
||||||
if _, ok := curveForCurveID(curveID); !ok {
|
|
||||||
c.sendAlert(alertInternalError)
|
c.sendAlert(alertInternalError)
|
||||||
return errors.New("tls: CurvePreferences includes unsupported curve")
|
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 {
|
if err != nil {
|
||||||
c.sendAlert(alertInternalError)
|
c.sendAlert(alertInternalError)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
hs.keyShareKeys = &keySharePrivateKeys{curveID: curveID, ecdhe: key}
|
// Do not send the fallback ECDH key share in a HRR response.
|
||||||
hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}}
|
hello.keyShares = hello.keyShares[:1]
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(hello.pskIdentities) > 0 {
|
if len(hello.pskIdentities) > 0 {
|
||||||
@@ -475,36 +470,16 @@ func (hs *clientHandshakeStateTLS13) processServerHello() error {
|
|||||||
func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
|
func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
|
||||||
c := hs.c
|
c := hs.c
|
||||||
|
|
||||||
ecdhePeerData := hs.serverHello.serverShare.data
|
ke, err := keyExchangeForCurveID(hs.serverHello.serverShare.group)
|
||||||
if hs.serverHello.serverShare.group == X25519MLKEM768 {
|
if err != nil {
|
||||||
if len(ecdhePeerData) != mlkem.CiphertextSize768+x25519PublicKeySize {
|
c.sendAlert(alertInternalError)
|
||||||
c.sendAlert(alertIllegalParameter)
|
return err
|
||||||
return errors.New("tls: invalid server X25519MLKEM768 key share")
|
|
||||||
}
|
|
||||||
ecdhePeerData = hs.serverHello.serverShare.data[mlkem.CiphertextSize768:]
|
|
||||||
}
|
}
|
||||||
peerKey, err := hs.keyShareKeys.ecdhe.Curve().NewPublicKey(ecdhePeerData)
|
sharedKey, err := ke.clientSharedSecret(hs.keyShareKeys, hs.serverHello.serverShare.data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.sendAlert(alertIllegalParameter)
|
c.sendAlert(alertIllegalParameter)
|
||||||
return errors.New("tls: invalid server key share")
|
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
|
c.curveID = hs.serverHello.serverShare.group
|
||||||
|
|
||||||
earlySecret := hs.earlySecret
|
earlySecret := hs.earlySecret
|
||||||
|
|||||||
@@ -340,55 +340,16 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
|
|||||||
}
|
}
|
||||||
c.curveID = selectedGroup
|
c.curveID = selectedGroup
|
||||||
|
|
||||||
ecdhGroup := selectedGroup
|
ke, err := keyExchangeForCurveID(selectedGroup)
|
||||||
ecdhData := clientKeyShare.data
|
if err != nil {
|
||||||
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 {
|
|
||||||
c.sendAlert(alertInternalError)
|
c.sendAlert(alertInternalError)
|
||||||
return errors.New("tls: CurvePreferences includes unsupported curve")
|
return errors.New("tls: CurvePreferences includes unsupported curve")
|
||||||
}
|
}
|
||||||
key, err := generateECDHEKey(c.config.rand(), ecdhGroup)
|
hs.sharedKey, hs.hello.serverShare, err = ke.serverSharedSecret(c.config.rand(), clientKeyShare.data)
|
||||||
if err != nil {
|
|
||||||
c.sendAlert(alertInternalError)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
hs.hello.serverShare = keyShare{group: selectedGroup, data: key.PublicKey().Bytes()}
|
|
||||||
peerKey, err := key.Curve().NewPublicKey(ecdhData)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.sendAlert(alertIllegalParameter)
|
c.sendAlert(alertIllegalParameter)
|
||||||
return errors.New("tls: invalid client key share")
|
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)
|
selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+29
-3
@@ -159,17 +159,17 @@ func hashForServerKeyExchange(sigType uint8, hashFunc crypto.Hash, version uint1
|
|||||||
type ecdheKeyAgreement struct {
|
type ecdheKeyAgreement struct {
|
||||||
version uint16
|
version uint16
|
||||||
isRSA bool
|
isRSA bool
|
||||||
key *ecdh.PrivateKey
|
|
||||||
|
|
||||||
// ckx and preMasterSecret are generated in processServerKeyExchange
|
// ckx and preMasterSecret are generated in processServerKeyExchange
|
||||||
// and returned in generateClientKeyExchange.
|
// and returned in generateClientKeyExchange.
|
||||||
ckx *clientKeyExchangeMsg
|
ckx *clientKeyExchangeMsg
|
||||||
preMasterSecret []byte
|
preMasterSecret []byte
|
||||||
|
|
||||||
// curveID and signatureAlgorithm are set by processServerKeyExchange and
|
// curveID, signatureAlgorithm, and key are set by processServerKeyExchange
|
||||||
// generateServerKeyExchange.
|
// and generateServerKeyExchange.
|
||||||
curveID CurveID
|
curveID CurveID
|
||||||
signatureAlgorithm SignatureScheme
|
signatureAlgorithm SignatureScheme
|
||||||
|
key *ecdh.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
|
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
|
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
@@ -5,6 +5,7 @@
|
|||||||
package reality
|
package reality
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"crypto/ecdh"
|
"crypto/ecdh"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/mlkem"
|
"crypto/mlkem"
|
||||||
@@ -51,35 +52,202 @@ func (c *cipherSuiteTLS13) exportKeyingMaterial(s *tls13.MasterSecret, transcrip
|
|||||||
}
|
}
|
||||||
|
|
||||||
type keySharePrivateKeys struct {
|
type keySharePrivateKeys struct {
|
||||||
curveID CurveID
|
ecdhe *ecdh.PrivateKey
|
||||||
ecdhe *ecdh.PrivateKey
|
mlkem crypto.Decapsulator
|
||||||
mlkem *mlkem.DecapsulationKey768
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// serverSharedSecret computes the shared secret and the server's key share.
|
||||||
// according to RFC 8446, Section 4.2.8.2.
|
serverSharedSecret(rand io.Reader, clientKeyShare []byte) ([]byte, keyShare, error)
|
||||||
func generateECDHEKey(rand io.Reader, curveID CurveID) (*ecdh.PrivateKey, error) {
|
|
||||||
curve, ok := curveForCurveID(curveID)
|
// clientSharedSecret computes the shared secret given the server's key
|
||||||
if !ok {
|
// share and the keys generated by keyShares.
|
||||||
return nil, errors.New("tls: internal error: unsupported curve")
|
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 {
|
switch id {
|
||||||
case X25519:
|
case X25519:
|
||||||
return ecdh.X25519(), true
|
return &ecdhKeyExchange{id, ecdh.X25519()}, nil
|
||||||
case CurveP256:
|
case CurveP256:
|
||||||
return ecdh.P256(), true
|
return &ecdhKeyExchange{id, ecdh.P256()}, nil
|
||||||
case CurveP384:
|
case CurveP384:
|
||||||
return ecdh.P384(), true
|
return &ecdhKeyExchange{id, ecdh.P384()}, nil
|
||||||
case CurveP521:
|
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:
|
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