mirror of
https://github.com/refraction-networking/utls.git
synced 2025-04-04 04:27:36 +03:00
crypto/tls: Add hybrid post-quantum key agreement (#13)
* import: client-side KEM from cloudflare/go * import: server-side KEM from cloudflare/go * fix: modify test to get rid of CFEvents. Note: uTLS does not promise any server-side functionality, and this change is made to be able to conduct unit tests which requires both side to be able to handle KEM Curves. Co-authored-by: Christopher Wood <caw@heapingbits.net> Co-authored-by: bwesterb <bas@westerbaan.name> ---- Based on: * crypto/tls: Add hybrid post-quantum key agreement Adds X25519Kyber512Draft00, X25519Kyber768Draft00, and P256Kyber768Draft00 hybrid post-quantum key agreements with temporary group identifiers. The hybrid post-quantum key exchanges uses plain X{25519,448} instead of HPKE, which we assume will be more likely to be adopted. The order is chosen to match CECPQ2. Not enabled by default. Adds CFEvents to detect `HelloRetryRequest`s and to signal which key agreement was used. Co-authored-by: Christopher Wood <caw@heapingbits.net> [bas, 1.20.1: also adds P256Kyber768Draft00] [pwu, 1.20.4: updated circl to v1.3.3, moved code to cfevent.go]
This commit is contained in:
parent
8199306255
commit
f9a1a7f1af
10 changed files with 412 additions and 62 deletions
113
cfkem.go
Normal file
113
cfkem.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2022 Cloudflare, Inc. All rights reserved. Use of this source code
|
||||
// is governed by a BSD-style license that can be found in the LICENSE file.
|
||||
//
|
||||
// Glue to add Circl's (post-quantum) hybrid KEMs.
|
||||
//
|
||||
// To enable set CurvePreferences with the desired scheme as the first element:
|
||||
//
|
||||
// import (
|
||||
// "github.com/cloudflare/circl/kem/tls"
|
||||
// "github.com/cloudflare/circl/kem/hybrid"
|
||||
//
|
||||
// [...]
|
||||
//
|
||||
// config.CurvePreferences = []tls.CurveID{
|
||||
// hybrid.X25519Kyber512Draft00().(tls.TLSScheme).TLSCurveID(),
|
||||
// tls.X25519,
|
||||
// tls.P256,
|
||||
// }
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"crypto/ecdh"
|
||||
|
||||
"github.com/cloudflare/circl/kem"
|
||||
"github.com/cloudflare/circl/kem/hybrid"
|
||||
)
|
||||
|
||||
// Either ecdheParameters or kem.PrivateKey
|
||||
type clientKeySharePrivate interface{}
|
||||
|
||||
var (
|
||||
X25519Kyber512Draft00 = CurveID(0xfe30)
|
||||
X25519Kyber768Draft00 = CurveID(0xfe31)
|
||||
P256Kyber768Draft00 = CurveID(0xfe32)
|
||||
invalidCurveID = CurveID(0)
|
||||
)
|
||||
|
||||
func kemSchemeKeyToCurveID(s kem.Scheme) CurveID {
|
||||
switch s.Name() {
|
||||
case "Kyber512-X25519":
|
||||
return X25519Kyber512Draft00
|
||||
case "Kyber768-X25519":
|
||||
return X25519Kyber768Draft00
|
||||
case "P256Kyber768Draft00":
|
||||
return P256Kyber768Draft00
|
||||
default:
|
||||
return invalidCurveID
|
||||
}
|
||||
}
|
||||
|
||||
// Extract CurveID from clientKeySharePrivate
|
||||
func clientKeySharePrivateCurveID(ks clientKeySharePrivate) CurveID {
|
||||
switch v := ks.(type) {
|
||||
case kem.PrivateKey:
|
||||
ret := kemSchemeKeyToCurveID(v.Scheme())
|
||||
if ret == invalidCurveID {
|
||||
panic("cfkem: internal error: don't know CurveID for this KEM")
|
||||
}
|
||||
return ret
|
||||
case *ecdh.PrivateKey:
|
||||
ret, ok := curveIDForCurve(v.Curve())
|
||||
if !ok {
|
||||
panic("cfkem: internal error: unknown curve")
|
||||
}
|
||||
return ret
|
||||
default:
|
||||
panic("cfkem: internal error: unknown clientKeySharePrivate")
|
||||
}
|
||||
}
|
||||
|
||||
// Returns scheme by CurveID if supported by Circl
|
||||
func curveIdToCirclScheme(id CurveID) kem.Scheme {
|
||||
switch id {
|
||||
case X25519Kyber512Draft00:
|
||||
return hybrid.Kyber512X25519()
|
||||
case X25519Kyber768Draft00:
|
||||
return hybrid.Kyber768X25519()
|
||||
case P256Kyber768Draft00:
|
||||
return hybrid.P256Kyber768Draft00()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate a new shared secret and encapsulates it for the packed
|
||||
// public key in ppk using randomness from rnd.
|
||||
func encapsulateForKem(scheme kem.Scheme, rnd io.Reader, ppk []byte) (
|
||||
ct, ss []byte, alert alert, err error) {
|
||||
pk, err := scheme.UnmarshalBinaryPublicKey(ppk)
|
||||
if err != nil {
|
||||
return nil, nil, alertIllegalParameter, fmt.Errorf("unpack pk: %w", err)
|
||||
}
|
||||
seed := make([]byte, scheme.EncapsulationSeedSize())
|
||||
if _, err := io.ReadFull(rnd, seed); err != nil {
|
||||
return nil, nil, alertInternalError, fmt.Errorf("random: %w", err)
|
||||
}
|
||||
ct, ss, err = scheme.EncapsulateDeterministically(pk, seed)
|
||||
return ct, ss, alertIllegalParameter, err
|
||||
}
|
||||
|
||||
// Generate a new keypair using randomness from rnd.
|
||||
func generateKemKeyPair(scheme kem.Scheme, rnd io.Reader) (
|
||||
kem.PublicKey, kem.PrivateKey, error) {
|
||||
seed := make([]byte, scheme.SeedSize())
|
||||
if _, err := io.ReadFull(rnd, seed); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pk, sk := scheme.DeriveKeyPair(seed)
|
||||
return pk, sk, nil
|
||||
}
|
121
cfkem_test.go
Normal file
121
cfkem_test.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
// Copyright 2022 Cloudflare, Inc. All rights reserved. Use of this source code
|
||||
// is governed by a BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/circl/kem"
|
||||
"github.com/cloudflare/circl/kem/hybrid"
|
||||
)
|
||||
|
||||
func testHybridKEX(t *testing.T, scheme kem.Scheme, clientPQ, serverPQ,
|
||||
clientTLS12, serverTLS12 bool) {
|
||||
// var clientSelectedKEX *CurveID
|
||||
// var retry bool
|
||||
|
||||
rsaCert := Certificate{
|
||||
Certificate: [][]byte{testRSACertificate},
|
||||
PrivateKey: testRSAPrivateKey,
|
||||
}
|
||||
serverCerts := []Certificate{rsaCert}
|
||||
|
||||
clientConfig := testConfig.Clone()
|
||||
if clientPQ {
|
||||
clientConfig.CurvePreferences = []CurveID{
|
||||
kemSchemeKeyToCurveID(scheme),
|
||||
X25519,
|
||||
}
|
||||
}
|
||||
// clientCFEventHandler := func(ev CFEvent) {
|
||||
// switch e := ev.(type) {
|
||||
// case CFEventTLSNegotiatedNamedKEX:
|
||||
// clientSelectedKEX = &e.KEX
|
||||
// case CFEventTLS13HRR:
|
||||
// retry = true
|
||||
// }
|
||||
// }
|
||||
if clientTLS12 {
|
||||
clientConfig.MaxVersion = VersionTLS12
|
||||
}
|
||||
|
||||
serverConfig := testConfig.Clone()
|
||||
if serverPQ {
|
||||
serverConfig.CurvePreferences = []CurveID{
|
||||
kemSchemeKeyToCurveID(scheme),
|
||||
X25519,
|
||||
}
|
||||
}
|
||||
if serverTLS12 {
|
||||
serverConfig.MaxVersion = VersionTLS12
|
||||
}
|
||||
serverConfig.Certificates = serverCerts
|
||||
|
||||
c, s := localPipe(t)
|
||||
done := make(chan error)
|
||||
defer c.Close()
|
||||
|
||||
go func() {
|
||||
defer s.Close()
|
||||
done <- Server(s, serverConfig).Handshake()
|
||||
}()
|
||||
|
||||
cli := Client(c, clientConfig)
|
||||
// cCtx := context.WithValue(context.Background(), CFEventHandlerContextKey{}, clientCFEventHandler)
|
||||
clientErr := cli.HandshakeContext(context.Background())
|
||||
serverErr := <-done
|
||||
if clientErr != nil {
|
||||
t.Errorf("client error: %s", clientErr)
|
||||
}
|
||||
if serverErr != nil {
|
||||
t.Errorf("server error: %s", serverErr)
|
||||
}
|
||||
|
||||
// var expectedKEX CurveID
|
||||
// var expectedRetry bool
|
||||
|
||||
// if clientPQ && serverPQ && !clientTLS12 && !serverTLS12 {
|
||||
// expectedKEX = kemSchemeKeyToCurveID(scheme)
|
||||
// } else {
|
||||
// expectedKEX = X25519
|
||||
// }
|
||||
// if !clientTLS12 && clientPQ && !serverPQ {
|
||||
// expectedRetry = true
|
||||
// }
|
||||
|
||||
// if clientSelectedKEX == nil {
|
||||
// t.Error("No KEX happened?")
|
||||
// }
|
||||
|
||||
// if *clientSelectedKEX != expectedKEX {
|
||||
// t.Errorf("failed to negotiate: expected %d, got %d",
|
||||
// expectedKEX, *clientSelectedKEX)
|
||||
// }
|
||||
// if expectedRetry != retry {
|
||||
// t.Errorf("Expected retry=%v, got retry=%v", expectedRetry, retry)
|
||||
// }
|
||||
}
|
||||
|
||||
func TestHybridKEX(t *testing.T) {
|
||||
run := func(scheme kem.Scheme, clientPQ, serverPQ, clientTLS12, serverTLS12 bool) {
|
||||
t.Run(fmt.Sprintf("%s serverPQ:%v clientPQ:%v serverTLS12:%v clientTLS12:%v", scheme.Name(),
|
||||
serverPQ, clientPQ, serverTLS12, clientTLS12), func(t *testing.T) {
|
||||
testHybridKEX(t, scheme, clientPQ, serverPQ, clientTLS12, serverTLS12)
|
||||
})
|
||||
}
|
||||
for _, scheme := range []kem.Scheme{
|
||||
hybrid.Kyber512X25519(),
|
||||
hybrid.Kyber768X25519(),
|
||||
hybrid.P256Kyber768Draft00(),
|
||||
} {
|
||||
run(scheme, true, true, false, false)
|
||||
run(scheme, true, false, false, false)
|
||||
run(scheme, false, true, false, false)
|
||||
run(scheme, true, true, true, false)
|
||||
run(scheme, true, true, false, true)
|
||||
run(scheme, true, true, true, true)
|
||||
}
|
||||
}
|
1
go.mod
1
go.mod
|
@ -9,6 +9,7 @@ retract (
|
|||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5
|
||||
github.com/cloudflare/circl v1.3.3
|
||||
github.com/gaukas/godicttls v0.0.4
|
||||
github.com/klauspost/compress v1.16.7
|
||||
github.com/quic-go/quic-go v0.37.4
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1,5 +1,7 @@
|
|||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
|
|
|
@ -21,6 +21,8 @@ import (
|
|||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
circlKem "github.com/cloudflare/circl/kem"
|
||||
)
|
||||
|
||||
type clientHandshakeState struct {
|
||||
|
@ -39,7 +41,7 @@ type clientHandshakeState struct {
|
|||
|
||||
var testingOnlyForceClientHelloSignatureAlgorithms []SignatureScheme
|
||||
|
||||
func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
|
||||
func (c *Conn) makeClientHello() (*clientHelloMsg, clientKeySharePrivate, error) {
|
||||
config := c.config
|
||||
|
||||
// [UTLS SECTION START]
|
||||
|
@ -136,7 +138,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
|
|||
hello.supportedSignatureAlgorithms = testingOnlyForceClientHelloSignatureAlgorithms
|
||||
}
|
||||
|
||||
var key *ecdh.PrivateKey
|
||||
var secret clientKeySharePrivate
|
||||
if hello.supportedVersions[0] == VersionTLS13 {
|
||||
// Reset the list of ciphers when the client only supports TLS 1.3.
|
||||
if len(hello.supportedVersions) == 1 {
|
||||
|
@ -149,14 +151,28 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
|
|||
}
|
||||
|
||||
curveID := config.curvePreferences()[0]
|
||||
if _, ok := curveForCurveID(curveID); !ok {
|
||||
return nil, nil, errors.New("tls: CurvePreferences includes unsupported curve")
|
||||
if scheme := curveIdToCirclScheme(curveID); scheme != nil {
|
||||
pk, sk, err := generateKemKeyPair(scheme, config.rand())
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("generateKemKeyPair %s: %w", scheme.Name(), err)
|
||||
}
|
||||
packedPk, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("pack circl public key %s: %w", scheme.Name(), err)
|
||||
}
|
||||
hello.keyShares = []keyShare{{group: curveID, data: packedPk}}
|
||||
secret = sk
|
||||
} else {
|
||||
if _, ok := curveForCurveID(curveID); !ok {
|
||||
return nil, nil, errors.New("tls: CurvePreferences includes unsupported curve")
|
||||
}
|
||||
key, err := generateECDHEKey(config.rand(), curveID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}}
|
||||
secret = key
|
||||
}
|
||||
key, err = generateECDHEKey(config.rand(), curveID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}}
|
||||
}
|
||||
|
||||
if c.quic != nil {
|
||||
|
@ -170,7 +186,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
|
|||
hello.quicTransportParameters = p
|
||||
}
|
||||
|
||||
return hello, key, nil
|
||||
return hello, secret, nil
|
||||
}
|
||||
|
||||
func (c *Conn) clientHandshake(ctx context.Context) (err error) {
|
||||
|
@ -182,7 +198,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
|
|||
// need to be reset.
|
||||
c.didResume = false
|
||||
|
||||
hello, ecdheKey, err := c.makeClientHello()
|
||||
hello, keySharePrivate, err := c.makeClientHello()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -256,7 +272,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
|
|||
ctx: ctx,
|
||||
serverHello: serverHello,
|
||||
hello: hello,
|
||||
ecdheKey: ecdheKey,
|
||||
// ecdheKey: ecdheKey,
|
||||
session: session,
|
||||
earlySecret: earlySecret,
|
||||
binderKey: binderKey,
|
||||
|
@ -264,6 +280,14 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
|
|||
keySharesEcdheParams: make(KeySharesEcdheParameters, 2), // [uTLS]
|
||||
}
|
||||
|
||||
if ecdheKey, ok := keySharePrivate.(*ecdh.PrivateKey); ok {
|
||||
hs.ecdheKey = ecdheKey
|
||||
} else if kemKey, ok := keySharePrivate.(circlKem.PrivateKey); ok {
|
||||
hs.kemKey = kemKey
|
||||
} else {
|
||||
return fmt.Errorf("tls: unknown key share type %T", keySharePrivate)
|
||||
}
|
||||
|
||||
// In TLS 1.3, session tickets are delivered after the handshake.
|
||||
return hs.handshake()
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ import (
|
|||
"fmt"
|
||||
"hash"
|
||||
"time"
|
||||
|
||||
circlKem "github.com/cloudflare/circl/kem"
|
||||
)
|
||||
|
||||
// [uTLS SECTION START]
|
||||
|
@ -35,17 +37,19 @@ func (keymap KeySharesEcdheParameters) GetPublicEcdheParams(curveID CurveID) (pa
|
|||
// [uTLS SECTION END]
|
||||
|
||||
type clientHandshakeStateTLS13 struct {
|
||||
c *Conn
|
||||
ctx context.Context
|
||||
serverHello *serverHelloMsg
|
||||
hello *clientHelloMsg
|
||||
ecdheKey *ecdh.PrivateKey
|
||||
|
||||
c *Conn
|
||||
ctx context.Context
|
||||
serverHello *serverHelloMsg
|
||||
hello *clientHelloMsg
|
||||
ecdheKey *ecdh.PrivateKey
|
||||
keySharesEcdheParams KeySharesEcdheParameters // [uTLS]
|
||||
kemKey circlKem.PrivateKey // [uTLS]
|
||||
// keySharesCirclParams KeySharesCirclParameters // [uTLS] TODO: perhaps implement?
|
||||
|
||||
session *SessionState
|
||||
earlySecret []byte
|
||||
binderKey []byte
|
||||
session *SessionState
|
||||
earlySecret []byte
|
||||
binderKey []byte
|
||||
selectedGroup CurveID
|
||||
|
||||
certReq *certificateRequestMsgTLS13
|
||||
usingPSK bool
|
||||
|
@ -83,7 +87,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
|
|||
// [uTLS SECTION END]
|
||||
|
||||
// Consistency check on the presence of a keyShare and its parameters.
|
||||
if hs.ecdheKey == nil || len(hs.hello.keyShares) < 1 { // [uTLS]
|
||||
if (hs.ecdheKey == nil && hs.kemKey == nil) || len(hs.hello.keyShares) < 1 { // [uTLS]
|
||||
// keyshares "< 1" instead of "!= 1", as uTLS may send multiple
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
|
@ -268,21 +272,55 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
|
|||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected unsupported group")
|
||||
}
|
||||
if sentID, _ := curveIDForCurve(hs.ecdheKey.Curve()); sentID == curveID {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share")
|
||||
}
|
||||
if _, ok := curveForCurveID(curveID); !ok {
|
||||
|
||||
// [UTLS SECTION BEGINS]
|
||||
// ported from cloudflare/go, slightly modified to maintain compatibility with crypto/tls upstream
|
||||
if hs.ecdheKey != nil {
|
||||
if sentID, _ := curveIDForCurve(hs.ecdheKey.Curve()); sentID == curveID {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share")
|
||||
}
|
||||
} else if hs.kemKey != nil {
|
||||
if clientKeySharePrivateCurveID(hs.kemKey) == curveID {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share")
|
||||
}
|
||||
} else {
|
||||
c.sendAlert(alertInternalError)
|
||||
return errors.New("tls: CurvePreferences includes unsupported curve")
|
||||
return errors.New("tls: ecdheKey and kemKey are both nil")
|
||||
}
|
||||
key, err := generateECDHEKey(c.config.rand(), curveID)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
|
||||
if scheme := curveIdToCirclScheme(curveID); scheme != nil {
|
||||
pk, sk, err := generateKemKeyPair(scheme, c.config.rand())
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return fmt.Errorf("HRR generateKemKeyPair %s: %w",
|
||||
scheme.Name(), err)
|
||||
}
|
||||
packedPk, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return fmt.Errorf("HRR pack circl public key %s: %w",
|
||||
scheme.Name(), err)
|
||||
}
|
||||
hs.kemKey = sk
|
||||
hs.ecdheKey = nil // unset ecdheKey if any
|
||||
hs.hello.keyShares = []keyShare{{group: curveID, data: packedPk}}
|
||||
} else {
|
||||
if _, ok := curveForCurveID(curveID); !ok {
|
||||
c.sendAlert(alertInternalError)
|
||||
return errors.New("tls: CurvePreferences includes unsupported curve")
|
||||
}
|
||||
key, err := generateECDHEKey(c.config.rand(), curveID)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
hs.ecdheKey = key
|
||||
hs.kemKey = nil // unset kemKey if any
|
||||
hs.hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}}
|
||||
}
|
||||
hs.ecdheKey = key
|
||||
hs.hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}}
|
||||
// [UTLS SECTION ENDS]
|
||||
}
|
||||
|
||||
hs.hello.raw = nil
|
||||
|
@ -430,9 +468,19 @@ func (hs *clientHandshakeStateTLS13) processServerHello() error {
|
|||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server did not send a key share")
|
||||
}
|
||||
if sentID, _ := curveIDForCurve(hs.ecdheKey.Curve()); hs.serverHello.serverShare.group != sentID {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected unsupported group")
|
||||
if hs.ecdheKey != nil {
|
||||
if sentID, _ := curveIDForCurve(hs.ecdheKey.Curve()); hs.serverHello.serverShare.group != sentID {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected unsupported group")
|
||||
}
|
||||
} else if hs.kemKey != nil {
|
||||
if clientKeySharePrivateCurveID(hs.kemKey) != hs.serverHello.serverShare.group {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected unsupported group")
|
||||
}
|
||||
} else {
|
||||
c.sendAlert(alertInternalError)
|
||||
return errors.New("tls: ecdheKey and kemKey are both nil")
|
||||
}
|
||||
|
||||
if !hs.serverHello.selectedIdentityPresent {
|
||||
|
@ -469,16 +517,33 @@ func (hs *clientHandshakeStateTLS13) processServerHello() error {
|
|||
func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
|
||||
c := hs.c
|
||||
|
||||
peerKey, err := hs.ecdheKey.Curve().NewPublicKey(hs.serverHello.serverShare.data)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid server key share")
|
||||
}
|
||||
sharedKey, err := hs.ecdheKey.ECDH(peerKey)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid server key share")
|
||||
// [UTLS SECTION BEGINS]
|
||||
// ported from cloudflare/go, slightly modified to maintain compatibility with crypto/tls upstream
|
||||
var sharedKey []byte
|
||||
var err error
|
||||
|
||||
if hs.ecdheKey != nil {
|
||||
peerKey, err := hs.ecdheKey.Curve().NewPublicKey(hs.serverHello.serverShare.data)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid server key share")
|
||||
}
|
||||
sharedKey, err = hs.ecdheKey.ECDH(peerKey)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid server key share")
|
||||
}
|
||||
} else if hs.kemKey != nil {
|
||||
sharedKey, err = hs.kemKey.Scheme().Decapsulate(hs.kemKey, hs.serverHello.serverShare.data)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return fmt.Errorf("%s decaps: %w", hs.kemKey.Scheme().Name(), err)
|
||||
}
|
||||
} else {
|
||||
c.sendAlert(alertInternalError)
|
||||
return errors.New("tls: ecdheKey and circlKey are both nil")
|
||||
}
|
||||
// [UTLS SECTION ENDS]
|
||||
|
||||
earlySecret := hs.earlySecret
|
||||
if !hs.usingPSK {
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"time"
|
||||
|
@ -33,6 +34,7 @@ type serverHandshakeStateTLS13 struct {
|
|||
suite *cipherSuiteTLS13
|
||||
cert *Certificate
|
||||
sigAlg SignatureScheme
|
||||
selectedGroup CurveID
|
||||
earlySecret []byte
|
||||
sharedKey []byte
|
||||
handshakeSecret []byte
|
||||
|
@ -211,23 +213,31 @@ GroupSelection:
|
|||
clientKeyShare = &hs.clientHello.keyShares[0]
|
||||
}
|
||||
|
||||
if _, ok := curveForCurveID(selectedGroup); !ok {
|
||||
if _, ok := curveForCurveID(selectedGroup); selectedGroup != X25519 && curveIdToCirclScheme(selectedGroup) == nil && !ok {
|
||||
c.sendAlert(alertInternalError)
|
||||
return errors.New("tls: CurvePreferences includes unsupported curve")
|
||||
}
|
||||
key, err := generateECDHEKey(c.config.rand(), selectedGroup)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
if kem := curveIdToCirclScheme(selectedGroup); kem != nil {
|
||||
ct, ss, alert, err := encapsulateForKem(kem, c.config.rand(), clientKeyShare.data)
|
||||
if err != nil {
|
||||
c.sendAlert(alert)
|
||||
return fmt.Errorf("%s encap: %w", kem.Name(), err)
|
||||
}
|
||||
hs.hello.serverShare = keyShare{group: selectedGroup, data: ct}
|
||||
hs.sharedKey = ss
|
||||
} else {
|
||||
key, err := generateECDHEKey(c.config.rand(), selectedGroup)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
hs.hello.serverShare = keyShare{group: selectedGroup, data: key.PublicKey().Bytes()}
|
||||
peerKey, err := key.Curve().NewPublicKey(clientKeyShare.data)
|
||||
if err == nil {
|
||||
hs.sharedKey, _ = key.ECDH(peerKey)
|
||||
}
|
||||
}
|
||||
hs.hello.serverShare = keyShare{group: selectedGroup, data: key.PublicKey().Bytes()}
|
||||
peerKey, err := key.Curve().NewPublicKey(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 {
|
||||
if hs.sharedKey == nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid client key share")
|
||||
}
|
||||
|
|
|
@ -169,7 +169,7 @@ type ecdheKeyAgreement struct {
|
|||
func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
|
||||
var curveID CurveID
|
||||
for _, c := range clientHello.supportedCurves {
|
||||
if config.supportsCurve(c) {
|
||||
if config.supportsCurve(c) && curveIdToCirclScheme(c) == nil {
|
||||
curveID = c
|
||||
break
|
||||
}
|
||||
|
|
13
u_conn.go
13
u_conn.go
|
@ -9,6 +9,7 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdh"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -16,6 +17,8 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
circlKem "github.com/cloudflare/circl/kem"
|
||||
)
|
||||
|
||||
type UConn struct {
|
||||
|
@ -76,13 +79,19 @@ func (uconn *UConn) BuildHandshakeState() error {
|
|||
}
|
||||
|
||||
// use default Golang ClientHello.
|
||||
hello, ecdheKey, err := uconn.makeClientHello()
|
||||
hello, keySharePrivate, err := uconn.makeClientHello()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uconn.HandshakeState.Hello = hello.getPublicPtr()
|
||||
uconn.HandshakeState.State13.EcdheKey = ecdheKey
|
||||
if ecdheKey, ok := keySharePrivate.(*ecdh.PrivateKey); ok {
|
||||
uconn.HandshakeState.State13.EcdheKey = ecdheKey
|
||||
} else if kemKey, ok := keySharePrivate.(circlKem.PrivateKey); ok {
|
||||
uconn.HandshakeState.State13.KEMKey = kemKey
|
||||
} else {
|
||||
|
||||
}
|
||||
uconn.HandshakeState.C = uconn.Conn
|
||||
} else {
|
||||
if !uconn.ClientHelloBuilt {
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
"crypto/x509"
|
||||
"hash"
|
||||
"time"
|
||||
|
||||
circlKem "github.com/cloudflare/circl/kem"
|
||||
)
|
||||
|
||||
// ClientHandshakeState includes both TLS 1.3-only and TLS 1.2-only states,
|
||||
|
@ -39,6 +41,7 @@ type TLS13OnlyState struct {
|
|||
Suite *PubCipherSuiteTLS13
|
||||
EcdheKey *ecdh.PrivateKey
|
||||
KeySharesEcdheParams KeySharesEcdheParameters
|
||||
KEMKey circlKem.PrivateKey
|
||||
EarlySecret []byte
|
||||
BinderKey []byte
|
||||
CertReq *CertificateRequestMsgTLS13
|
||||
|
@ -64,6 +67,7 @@ func (chs *PubClientHandshakeState) toPrivate13() *clientHandshakeStateTLS13 {
|
|||
hello: chs.Hello.getPrivatePtr(),
|
||||
ecdheKey: chs.State13.EcdheKey,
|
||||
keySharesEcdheParams: chs.State13.KeySharesEcdheParams,
|
||||
kemKey: chs.State13.KEMKey,
|
||||
|
||||
session: chs.Session,
|
||||
earlySecret: chs.State13.EarlySecret,
|
||||
|
@ -89,6 +93,7 @@ func (chs13 *clientHandshakeStateTLS13) toPublic13() *PubClientHandshakeState {
|
|||
tls13State := TLS13OnlyState{
|
||||
KeySharesEcdheParams: chs13.keySharesEcdheParams,
|
||||
EcdheKey: chs13.ecdheKey,
|
||||
KEMKey: chs13.kemKey,
|
||||
EarlySecret: chs13.earlySecret,
|
||||
BinderKey: chs13.binderKey,
|
||||
CertReq: chs13.certReq.toPublic(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue