mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00: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:
parent
a9093067ee
commit
1768cb40b8
16 changed files with 405 additions and 201 deletions
4
api/next/71206.txt
Normal file
4
api/next/71206.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pkg crypto/tls, const SecP256r1MLKEM768 = 4587 #71206
|
||||
pkg crypto/tls, const SecP256r1MLKEM768 CurveID #71206
|
||||
pkg crypto/tls, const SecP384r1MLKEM1024 = 4589 #71206
|
||||
pkg crypto/tls, const SecP384r1MLKEM1024 CurveID #71206
|
||||
|
|
@ -168,6 +168,10 @@ allows malformed hostnames containing colons outside of a bracketed IPv6 address
|
|||
The default `urlstrictcolons=1` rejects URLs such as `http://localhost:1:2` or `http://::1/`.
|
||||
Colons are permitted as part of a bracketed IPv6 address, such as `http://[::1]/`.
|
||||
|
||||
Go 1.26 enabled two additional post-quantum key exchange mechanisms:
|
||||
SecP256r1MLKEM768 and SecP384r1MLKEM1024. The default can be reverted using the
|
||||
[`tlssecpmlkem` setting](/pkg/crypto/tls/#Config.CurvePreferences).
|
||||
|
||||
Go 1.26 added a new `tracebacklabels` setting that controls the inclusion of
|
||||
goroutine labels set through the the `runtime/pprof` package. Setting `tracebacklabels=1`
|
||||
includes these key/value pairs in the goroutine status header of runtime
|
||||
|
|
|
|||
3
doc/next/6-stdlib/99-minor/crypto/tls/71206.md
Normal file
3
doc/next/6-stdlib/99-minor/crypto/tls/71206.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
The hybrid [SecP256r1MLKEM768] and [SecP384r1MLKEM1024] post-quantum key
|
||||
exchanges are now enabled by default. They can be disabled by setting
|
||||
[Config.CurvePreferences] or with the `tlssecpmlkem=0` GODEBUG setting.
|
||||
|
|
@ -219,7 +219,9 @@
|
|||
24,
|
||||
25,
|
||||
29,
|
||||
4588
|
||||
4587,
|
||||
4588,
|
||||
4589
|
||||
],
|
||||
"ErrorMap": {
|
||||
":ECH_REJECTED:": ["tls: server rejected ECH"]
|
||||
|
|
|
|||
|
|
@ -150,14 +150,26 @@ const (
|
|||
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.
|
||||
|
|
@ -787,6 +799,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.
|
||||
|
|
|
|||
|
|
@ -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) + ")"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,14 +14,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 tlsmlkem.Value() == "0" {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// defaultSupportedSignatureAlgorithms returns the signature and hash algorithms that
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ var (
|
|||
}
|
||||
allowedCurvePreferencesFIPS = []CurveID{
|
||||
X25519MLKEM768,
|
||||
SecP256r1MLKEM768,
|
||||
SecP384r1MLKEM1024,
|
||||
CurveP256,
|
||||
CurveP384,
|
||||
CurveP521,
|
||||
|
|
|
|||
|
|
@ -43,11 +43,15 @@ func isTLS13CipherSuite(id uint16) bool {
|
|||
}
|
||||
|
||||
func generateKeyShare(group CurveID) keyShare {
|
||||
key, err := generateECDHEKey(rand.Reader, group)
|
||||
ke, err := keyExchangeForCurveID(group)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return keyShare{group: group, data: key.PublicKey().Bytes()}
|
||||
_, shares, err := ke.keyShares(rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return shares[0]
|
||||
}
|
||||
|
||||
func TestFIPSServerProtocolVersion(t *testing.T) {
|
||||
|
|
@ -132,7 +136,7 @@ func isFIPSCurve(id CurveID) bool {
|
|||
switch id {
|
||||
case CurveP256, CurveP384, CurveP521:
|
||||
return true
|
||||
case X25519MLKEM768:
|
||||
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024:
|
||||
// Only for the native module.
|
||||
return !boring.Enabled
|
||||
case X25519:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import (
|
|||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/hpke"
|
||||
"crypto/internal/fips140/mlkem"
|
||||
"crypto/internal/fips140/tls13"
|
||||
"crypto/rsa"
|
||||
"crypto/subtle"
|
||||
|
|
@ -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)
|
||||
ke, err := keyExchangeForCurveID(curveID)
|
||||
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)
|
||||
keyShareKeys, hello.keyShares, err = ke.keyShares(config.rand())
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
hello.keyShares = []keyShare{{group: curveID, data: keyShareKeys.ecdhe.PublicKey().Bytes()}}
|
||||
// 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,7 +10,6 @@ import (
|
|||
"crypto"
|
||||
"crypto/hkdf"
|
||||
"crypto/hmac"
|
||||
"crypto/internal/fips140/mlkem"
|
||||
"crypto/internal/fips140/tls13"
|
||||
"crypto/rsa"
|
||||
"crypto/subtle"
|
||||
|
|
@ -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")
|
||||
ke, err := keyExchangeForCurveID(hs.serverHello.serverShare.group)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import (
|
|||
"crypto/hkdf"
|
||||
"crypto/hmac"
|
||||
"crypto/hpke"
|
||||
"crypto/internal/fips140/mlkem"
|
||||
"crypto/internal/fips140/tls13"
|
||||
"crypto/rsa"
|
||||
"crypto/tls/internal/fips140tls"
|
||||
|
|
@ -246,55 +245,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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -380,3 +380,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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdh"
|
||||
"crypto/hmac"
|
||||
"crypto/internal/fips140/mlkem"
|
||||
"crypto/internal/fips140/tls13"
|
||||
"crypto/mlkem"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
|
|
@ -50,35 +51,202 @@ func (c *cipherSuiteTLS13) exportKeyingMaterial(s *tls13.MasterSecret, transcrip
|
|||
}
|
||||
|
||||
type keySharePrivateKeys struct {
|
||||
curveID CurveID
|
||||
ecdhe *ecdh.PrivateKey
|
||||
mlkem *mlkem.DecapsulationKey768
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"crypto/ecdh"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/internal/boring"
|
||||
"crypto/rand"
|
||||
"crypto/tls/internal/fips140tls"
|
||||
"crypto/x509"
|
||||
|
|
@ -1964,84 +1965,134 @@ func testVerifyCertificates(t *testing.T, version uint16) {
|
|||
}
|
||||
|
||||
func TestHandshakeMLKEM(t *testing.T) {
|
||||
skipFIPS(t) // No X25519MLKEM768 in FIPS
|
||||
if boring.Enabled && fips140tls.Required() {
|
||||
t.Skip("ML-KEM not supported in BoringCrypto FIPS mode")
|
||||
}
|
||||
defaultWithPQ := []CurveID{X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024,
|
||||
X25519, CurveP256, CurveP384, CurveP521}
|
||||
defaultWithoutPQ := []CurveID{X25519, CurveP256, CurveP384, CurveP521}
|
||||
var tests = []struct {
|
||||
name string
|
||||
clientConfig func(*Config)
|
||||
serverConfig func(*Config)
|
||||
preparation func(*testing.T)
|
||||
expectClientSupport bool
|
||||
expectMLKEM bool
|
||||
expectClient []CurveID
|
||||
expectSelected CurveID
|
||||
expectHRR bool
|
||||
}{
|
||||
{
|
||||
name: "Default",
|
||||
expectClientSupport: true,
|
||||
expectMLKEM: true,
|
||||
expectHRR: false,
|
||||
expectClient: defaultWithPQ,
|
||||
expectSelected: X25519MLKEM768,
|
||||
},
|
||||
{
|
||||
name: "ClientCurvePreferences",
|
||||
clientConfig: func(config *Config) {
|
||||
config.CurvePreferences = []CurveID{X25519}
|
||||
},
|
||||
expectClientSupport: false,
|
||||
expectClient: []CurveID{X25519},
|
||||
expectSelected: X25519,
|
||||
},
|
||||
{
|
||||
name: "ServerCurvePreferencesX25519",
|
||||
serverConfig: func(config *Config) {
|
||||
config.CurvePreferences = []CurveID{X25519}
|
||||
},
|
||||
expectClientSupport: true,
|
||||
expectMLKEM: false,
|
||||
expectHRR: false,
|
||||
expectClient: defaultWithPQ,
|
||||
expectSelected: X25519,
|
||||
},
|
||||
{
|
||||
name: "ServerCurvePreferencesHRR",
|
||||
serverConfig: func(config *Config) {
|
||||
config.CurvePreferences = []CurveID{CurveP256}
|
||||
},
|
||||
expectClientSupport: true,
|
||||
expectMLKEM: false,
|
||||
expectClient: defaultWithPQ,
|
||||
expectSelected: CurveP256,
|
||||
expectHRR: true,
|
||||
},
|
||||
{
|
||||
name: "SecP256r1MLKEM768-Only",
|
||||
clientConfig: func(config *Config) {
|
||||
config.CurvePreferences = []CurveID{SecP256r1MLKEM768}
|
||||
},
|
||||
expectClient: []CurveID{SecP256r1MLKEM768},
|
||||
expectSelected: SecP256r1MLKEM768,
|
||||
},
|
||||
{
|
||||
name: "SecP256r1MLKEM768-HRR",
|
||||
serverConfig: func(config *Config) {
|
||||
config.CurvePreferences = []CurveID{SecP256r1MLKEM768, CurveP256}
|
||||
},
|
||||
expectClient: defaultWithPQ,
|
||||
expectSelected: SecP256r1MLKEM768,
|
||||
expectHRR: true,
|
||||
},
|
||||
{
|
||||
name: "SecP384r1MLKEM1024",
|
||||
clientConfig: func(config *Config) {
|
||||
config.CurvePreferences = []CurveID{SecP384r1MLKEM1024, CurveP384}
|
||||
},
|
||||
expectClient: []CurveID{SecP384r1MLKEM1024, CurveP384},
|
||||
expectSelected: SecP384r1MLKEM1024,
|
||||
},
|
||||
{
|
||||
name: "CurveP256NoHRR",
|
||||
clientConfig: func(config *Config) {
|
||||
config.CurvePreferences = []CurveID{SecP256r1MLKEM768, CurveP256}
|
||||
},
|
||||
serverConfig: func(config *Config) {
|
||||
config.CurvePreferences = []CurveID{CurveP256}
|
||||
},
|
||||
expectClient: []CurveID{SecP256r1MLKEM768, CurveP256},
|
||||
expectSelected: CurveP256,
|
||||
},
|
||||
{
|
||||
name: "ClientMLKEMOnly",
|
||||
clientConfig: func(config *Config) {
|
||||
config.CurvePreferences = []CurveID{X25519MLKEM768}
|
||||
},
|
||||
expectClientSupport: true,
|
||||
expectMLKEM: true,
|
||||
expectClient: []CurveID{X25519MLKEM768},
|
||||
expectSelected: X25519MLKEM768,
|
||||
},
|
||||
{
|
||||
name: "ClientSortedCurvePreferences",
|
||||
clientConfig: func(config *Config) {
|
||||
config.CurvePreferences = []CurveID{CurveP256, X25519MLKEM768}
|
||||
},
|
||||
expectClientSupport: true,
|
||||
expectMLKEM: true,
|
||||
expectClient: []CurveID{X25519MLKEM768, CurveP256},
|
||||
expectSelected: X25519MLKEM768,
|
||||
},
|
||||
{
|
||||
name: "ClientTLSv12",
|
||||
clientConfig: func(config *Config) {
|
||||
config.MaxVersion = VersionTLS12
|
||||
},
|
||||
expectClientSupport: false,
|
||||
expectClient: defaultWithoutPQ,
|
||||
expectSelected: X25519,
|
||||
},
|
||||
{
|
||||
name: "ServerTLSv12",
|
||||
serverConfig: func(config *Config) {
|
||||
config.MaxVersion = VersionTLS12
|
||||
},
|
||||
expectClientSupport: true,
|
||||
expectMLKEM: false,
|
||||
expectClient: defaultWithPQ,
|
||||
expectSelected: X25519,
|
||||
},
|
||||
{
|
||||
name: "GODEBUG",
|
||||
name: "GODEBUG tlsmlkem=0",
|
||||
preparation: func(t *testing.T) {
|
||||
t.Setenv("GODEBUG", "tlsmlkem=0")
|
||||
},
|
||||
expectClientSupport: false,
|
||||
expectClient: defaultWithoutPQ,
|
||||
expectSelected: X25519,
|
||||
},
|
||||
{
|
||||
name: "GODEBUG tlssecpmlkem=0",
|
||||
preparation: func(t *testing.T) {
|
||||
t.Setenv("GODEBUG", "tlssecpmlkem=0")
|
||||
},
|
||||
expectClient: []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521},
|
||||
expectSelected: X25519MLKEM768,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -2049,6 +2100,9 @@ func TestHandshakeMLKEM(t *testing.T) {
|
|||
baseConfig.CurvePreferences = nil
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if fips140tls.Required() && test.expectSelected == X25519 {
|
||||
t.Skip("X25519 not supported in FIPS mode")
|
||||
}
|
||||
if test.preparation != nil {
|
||||
test.preparation(t)
|
||||
} else {
|
||||
|
|
@ -2059,10 +2113,12 @@ func TestHandshakeMLKEM(t *testing.T) {
|
|||
test.serverConfig(serverConfig)
|
||||
}
|
||||
serverConfig.GetConfigForClient = func(hello *ClientHelloInfo) (*Config, error) {
|
||||
if !test.expectClientSupport && slices.Contains(hello.SupportedCurves, X25519MLKEM768) {
|
||||
return nil, errors.New("client supports X25519MLKEM768")
|
||||
} else if test.expectClientSupport && !slices.Contains(hello.SupportedCurves, X25519MLKEM768) {
|
||||
return nil, errors.New("client does not support X25519MLKEM768")
|
||||
expectClient := slices.Clone(test.expectClient)
|
||||
expectClient = slices.DeleteFunc(expectClient, func(c CurveID) bool {
|
||||
return fips140tls.Required() && c == X25519
|
||||
})
|
||||
if !slices.Equal(hello.SupportedCurves, expectClient) {
|
||||
t.Errorf("got client curves %v, expected %v", hello.SupportedCurves, expectClient)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -2074,20 +2130,11 @@ func TestHandshakeMLKEM(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if test.expectMLKEM {
|
||||
if ss.CurveID != X25519MLKEM768 {
|
||||
t.Errorf("got CurveID %v (server), expected %v", ss.CurveID, X25519MLKEM768)
|
||||
}
|
||||
if cs.CurveID != X25519MLKEM768 {
|
||||
t.Errorf("got CurveID %v (client), expected %v", cs.CurveID, X25519MLKEM768)
|
||||
}
|
||||
} else {
|
||||
if ss.CurveID == X25519MLKEM768 {
|
||||
t.Errorf("got CurveID %v (server), expected not X25519MLKEM768", ss.CurveID)
|
||||
}
|
||||
if cs.CurveID == X25519MLKEM768 {
|
||||
t.Errorf("got CurveID %v (client), expected not X25519MLKEM768", cs.CurveID)
|
||||
if ss.CurveID != test.expectSelected {
|
||||
t.Errorf("server selected curve %v, expected %v", ss.CurveID, test.expectSelected)
|
||||
}
|
||||
if cs.CurveID != test.expectSelected {
|
||||
t.Errorf("client selected curve %v, expected %v", cs.CurveID, test.expectSelected)
|
||||
}
|
||||
if test.expectHRR {
|
||||
if !ss.HelloRetryRequest {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ var All = []Info{
|
|||
{Name: "tlsmaxrsasize", Package: "crypto/tls"},
|
||||
{Name: "tlsmlkem", Package: "crypto/tls", Changed: 24, Old: "0", Opaque: true},
|
||||
{Name: "tlsrsakex", Package: "crypto/tls", Changed: 22, Old: "1"},
|
||||
{Name: "tlssecpmlkem", Package: "crypto/tls", Changed: 26, Old: "0", Opaque: true},
|
||||
{Name: "tlssha1", Package: "crypto/tls", Changed: 25, Old: "1"},
|
||||
{Name: "tlsunsafeekm", Package: "crypto/tls", Changed: 22, Old: "1"},
|
||||
{Name: "updatemaxprocs", Package: "runtime", Changed: 25, Old: "0"},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue