diff --git a/README.md b/README.md index 52fb63e..b0c1aed 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ### THE NEXT FUTURE -Server side implementation of REALITY protocol, a fork of package tls in Go 1.19.5. +Server side implementation of REALITY protocol, a fork of package tls in Go 1.20. For client side, please follow https://github.com/XTLS/Xray-core/blob/main/transport/internet/reality/reality.go. TODO List: TODO diff --git a/cache.go b/cache.go new file mode 100644 index 0000000..1aa8c5e --- /dev/null +++ b/cache.go @@ -0,0 +1,95 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE-Go file. + +package reality + +import ( + "crypto/x509" + "runtime" + "sync" + "sync/atomic" +) + +type cacheEntry struct { + refs atomic.Int64 + cert *x509.Certificate +} + +// certCache implements an intern table for reference counted x509.Certificates, +// implemented in a similar fashion to BoringSSL's CRYPTO_BUFFER_POOL. This +// allows for a single x509.Certificate to be kept in memory and referenced from +// multiple Conns. Returned references should not be mutated by callers. Certificates +// are still safe to use after they are removed from the cache. +// +// Certificates are returned wrapped in a activeCert struct that should be held by +// the caller. When references to the activeCert are freed, the number of references +// to the certificate in the cache is decremented. Once the number of references +// reaches zero, the entry is evicted from the cache. +// +// The main difference between this implementation and CRYPTO_BUFFER_POOL is that +// CRYPTO_BUFFER_POOL is a more generic structure which supports blobs of data, +// rather than specific structures. Since we only care about x509.Certificates, +// certCache is implemented as a specific cache, rather than a generic one. +// +// See https://boringssl.googlesource.com/boringssl/+/master/include/openssl/pool.h +// and https://boringssl.googlesource.com/boringssl/+/master/crypto/pool/pool.c +// for the BoringSSL reference. +type certCache struct { + sync.Map +} + +var clientCertCache = new(certCache) + +// activeCert is a handle to a certificate held in the cache. Once there are +// no alive activeCerts for a given certificate, the certificate is removed +// from the cache by a finalizer. +type activeCert struct { + cert *x509.Certificate +} + +// active increments the number of references to the entry, wraps the +// certificate in the entry in a activeCert, and sets the finalizer. +// +// Note that there is a race between active and the finalizer set on the +// returned activeCert, triggered if active is called after the ref count is +// decremented such that refs may be > 0 when evict is called. We consider this +// safe, since the caller holding an activeCert for an entry that is no longer +// in the cache is fine, with the only side effect being the memory overhead of +// there being more than one distinct reference to a certificate alive at once. +func (cc *certCache) active(e *cacheEntry) *activeCert { + e.refs.Add(1) + a := &activeCert{e.cert} + runtime.SetFinalizer(a, func(_ *activeCert) { + if e.refs.Add(-1) == 0 { + cc.evict(e) + } + }) + return a +} + +// evict removes a cacheEntry from the cache. +func (cc *certCache) evict(e *cacheEntry) { + cc.Delete(string(e.cert.Raw)) +} + +// newCert returns a x509.Certificate parsed from der. If there is already a copy +// of the certificate in the cache, a reference to the existing certificate will +// be returned. Otherwise, a fresh certificate will be added to the cache, and +// the reference returned. The returned reference should not be mutated. +func (cc *certCache) newCert(der []byte) (*activeCert, error) { + if entry, ok := cc.Load(string(der)); ok { + return cc.active(entry.(*cacheEntry)), nil + } + + cert, err := x509.ParseCertificate(der) + if err != nil { + return nil, err + } + + entry := &cacheEntry{cert: cert} + if entry, loaded := cc.LoadOrStore(string(der), entry); loaded { + return cc.active(entry.(*cacheEntry)), nil + } + return cc.active(entry), nil +} diff --git a/cipher_suites.go b/cipher_suites.go index 04b0060..571bab1 100644 --- a/cipher_suites.go +++ b/cipher_suites.go @@ -466,7 +466,7 @@ func (f *prefixNonceAEAD) Open(out, nonce, ciphertext, additionalData []byte) ([ return f.aead.Open(out, f.nonce[:], ciphertext, additionalData) } -// xoredNonceAEAD wraps an AEAD by XORing in a fixed pattern to the nonce +// xorNonceAEAD wraps an AEAD by XORing in a fixed pattern to the nonce // before each call. type xorNonceAEAD struct { nonceMask [aeadNonceLength]byte diff --git a/common.go b/common.go index 511c7b4..efbaaae 100644 --- a/common.go +++ b/common.go @@ -246,6 +246,8 @@ type ConnectionState struct { // On the client side, it can't be empty. On the server side, it can be // empty if Config.ClientAuth is not RequireAnyClientCert or // RequireAndVerifyClientCert. + // + // PeerCertificates and its contents should not be modified. PeerCertificates []*x509.Certificate // VerifiedChains is a list of one or more chains where the first element is @@ -255,6 +257,8 @@ type ConnectionState struct { // On the client side, it's set if Config.InsecureSkipVerify is false. On // the server side, it's set if Config.ClientAuth is VerifyClientCertIfGiven // (and the peer provided a certificate) or RequireAndVerifyClientCert. + // + // VerifiedChains and its contents should not be modified. VerifiedChains [][]*x509.Certificate // SignedCertificateTimestamps is a list of SCTs provided by the peer @@ -568,6 +572,8 @@ type Config struct { // If GetCertificate is nil or returns nil, then the certificate is // retrieved from NameToCertificate. If NameToCertificate is nil, the // best element of Certificates will be used. + // + // Once a Certificate is returned it should not be modified. GetCertificate func(*ClientHelloInfo) (*Certificate, error) // GetClientCertificate, if not nil, is called when a server requests a @@ -583,6 +589,8 @@ type Config struct { // // GetClientCertificate may be called multiple times for the same // connection if renegotiation occurs or if TLS 1.3 is in use. + // + // Once a Certificate is returned it should not be modified. GetClientCertificate func(*CertificateRequestInfo) (*Certificate, error) // GetConfigForClient, if not nil, is called after a ClientHello is @@ -611,6 +619,8 @@ type Config struct { // setting InsecureSkipVerify, or (for a server) when ClientAuth is // RequestClientCert or RequireAnyClientCert, then this callback will // be considered but the verifiedChains argument will always be nil. + // + // verifiedChains and its contents should not be modified. VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error // VerifyConnection, if not nil, is called after normal certificate @@ -739,7 +749,7 @@ type Config struct { // mutex protects sessionTicketKeys and autoSessionTicketKeys. mutex sync.RWMutex - // sessionTicketKeys contains zero or more ticket keys. If set, it means the + // sessionTicketKeys contains zero or more ticket keys. If set, it means // the keys were set with SessionTicketKey or SetSessionTicketKeys. The // first key is used for new tickets and any subsequent keys can be used to // decrypt old tickets. The slice contents are not protected by the mutex @@ -1363,7 +1373,7 @@ func (c *Config) writeKeyLog(label string, clientRandom, secret []byte) error { return nil } - logLine := []byte(fmt.Sprintf("%s %x %x\n", label, clientRandom, secret)) + logLine := fmt.Appendf(nil, "%s %x %x\n", label, clientRandom, secret) writerMutex.Lock() _, err := c.KeyLogWriter.Write(logLine) @@ -1508,3 +1518,18 @@ func isSupportedSignatureAlgorithm(sigAlg SignatureScheme, supportedSignatureAlg } return false } + +// CertificateVerificationError is returned when certificate verification fails during the handshake. +type CertificateVerificationError struct { + // UnverifiedCertificates and its contents should not be modified. + UnverifiedCertificates []*x509.Certificate + Err error +} + +func (e *CertificateVerificationError) Error() string { + return fmt.Sprintf("tls: failed to verify certificate: %s", e.Err) +} + +func (e *CertificateVerificationError) Unwrap() error { + return e.Err +} diff --git a/conn.go b/conn.go index fd92b54..c5a5fbf 100644 --- a/conn.go +++ b/conn.go @@ -35,11 +35,10 @@ type Conn struct { isClient bool handshakeFn func(context.Context) error // (*Conn).clientHandshake or serverHandshake - // handshakeStatus is 1 if the connection is currently transferring + // isHandshakeComplete is true if the connection is currently transferring // application data (i.e. is not currently processing a handshake). - // handshakeStatus == 1 implies handshakeErr == nil. - // This field is only to be accessed with sync/atomic. - handshakeStatus uint32 + // isHandshakeComplete is true implies handshakeErr == nil. + isHandshakeComplete atomic.Bool // constant after handshake; protected by handshakeMutex handshakeMutex sync.Mutex handshakeErr error // error resulting from handshake @@ -55,6 +54,9 @@ type Conn struct { ocspResponse []byte // stapled OCSP response scts [][]byte // signed certificate timestamps from server peerCertificates []*x509.Certificate + // activeCertHandles contains the cache handles to certificates in + // peerCertificates that are used to track active references. + activeCertHandles []*activeCert // verifiedChains contains the certificate chains that we built, as // opposed to the ones presented by the server. verifiedChains [][]*x509.Certificate @@ -115,10 +117,9 @@ type Conn struct { // handshake, nor deliver application data. Protected by in.Mutex. retryCount int - // activeCall is an atomic int32; the low bit is whether Close has - // been called. the rest of the bits are the number of goroutines - // in Conn.Write. - activeCall int32 + // activeCall indicates whether Close has been call in the low bit. + // the rest of the bits are the number of goroutines in Conn.Write. + activeCall atomic.Int32 tmp [16]byte } @@ -639,7 +640,7 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { if c.in.err != nil { return c.in.err } - handshakeComplete := c.handshakeComplete() + handshakeComplete := c.isHandshakeComplete.Load() // This function modifies c.rawInput, which owns the c.input memory. if c.input.Len() != 0 { @@ -1154,15 +1155,15 @@ var ( func (c *Conn) Write(b []byte) (int, error) { // interlock with Close below for { - x := atomic.LoadInt32(&c.activeCall) + x := c.activeCall.Load() if x&1 != 0 { return 0, net.ErrClosed } - if atomic.CompareAndSwapInt32(&c.activeCall, x, x+2) { + if c.activeCall.CompareAndSwap(x, x+2) { break } } - defer atomic.AddInt32(&c.activeCall, -2) + defer c.activeCall.Add(-2) if err := c.Handshake(); err != nil { return 0, err @@ -1175,7 +1176,7 @@ func (c *Conn) Write(b []byte) (int, error) { return 0, err } - if !c.handshakeComplete() { + if !c.isHandshakeComplete.Load() { return 0, alertInternalError } @@ -1245,7 +1246,7 @@ func (c *Conn) handleRenegotiation() error { c.handshakeMutex.Lock() defer c.handshakeMutex.Unlock() - atomic.StoreUint32(&c.handshakeStatus, 0) + c.isHandshakeComplete.Store(false) if c.handshakeErr = c.clientHandshake(context.Background()); c.handshakeErr == nil { c.handshakes++ } @@ -1363,11 +1364,11 @@ func (c *Conn) Close() error { // Interlock with Conn.Write above. var x int32 for { - x = atomic.LoadInt32(&c.activeCall) + x = c.activeCall.Load() if x&1 != 0 { return net.ErrClosed } - if atomic.CompareAndSwapInt32(&c.activeCall, x, x|1) { + if c.activeCall.CompareAndSwap(x, x|1) { break } } @@ -1382,7 +1383,7 @@ func (c *Conn) Close() error { } var alertErr error - if c.handshakeComplete() { + if c.isHandshakeComplete.Load() { if err := c.closeNotify(); err != nil { alertErr = fmt.Errorf("tls: failed to send closeNotify alert (but connection was closed anyway): %w", err) } @@ -1400,7 +1401,7 @@ var errEarlyCloseWrite = errors.New("tls: CloseWrite called before handshake com // called once the handshake has completed and does not call CloseWrite on the // underlying connection. Most callers should just use Close. func (c *Conn) CloseWrite() error { - if !c.handshakeComplete() { + if !c.isHandshakeComplete.Load() { return errEarlyCloseWrite } @@ -1454,7 +1455,7 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) { // Fast sync/atomic-based exit if there is no handshake in flight and the // last one succeeded without an error. Avoids the expensive context setup // and mutex for most Read and Write calls. - if c.handshakeComplete() { + if c.isHandshakeComplete.Load() { return nil } @@ -1497,7 +1498,7 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) { if err := c.handshakeErr; err != nil { return err } - if c.handshakeComplete() { + if c.isHandshakeComplete.Load() { return nil } @@ -1513,10 +1514,10 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) { c.flush() } - if c.handshakeErr == nil && !c.handshakeComplete() { + if c.handshakeErr == nil && !c.isHandshakeComplete.Load() { c.handshakeErr = errors.New("tls: internal error: handshake should have had a result") } - if c.handshakeErr != nil && c.handshakeComplete() { + if c.handshakeErr != nil && c.isHandshakeComplete.Load() { panic("tls: internal error: handshake returned an error but is marked successful") } @@ -1532,7 +1533,7 @@ func (c *Conn) ConnectionState() ConnectionState { func (c *Conn) connectionStateLocked() ConnectionState { var state ConnectionState - state.HandshakeComplete = c.handshakeComplete() + state.HandshakeComplete = c.isHandshakeComplete.Load() state.Version = c.vers state.NegotiatedProtocol = c.clientProtocol state.DidResume = c.didResume @@ -1576,7 +1577,7 @@ func (c *Conn) VerifyHostname(host string) error { if !c.isClient { return errors.New("tls: VerifyHostname called on TLS server connection") } - if !c.handshakeComplete() { + if !c.isHandshakeComplete.Load() { return errors.New("tls: handshake has not yet been performed") } if len(c.verifiedChains) == 0 { @@ -1584,7 +1585,3 @@ func (c *Conn) VerifyHostname(host string) error { } return c.peerCertificates[0].VerifyHostname(host) } - -func (c *Conn) handshakeComplete() bool { - return atomic.LoadUint32(&c.handshakeStatus) == 1 -} diff --git a/generate_cert.go b/generate_cert.go index cc162ee..14351bb 100644 --- a/generate_cert.go +++ b/generate_cert.go @@ -156,7 +156,6 @@ func main() { keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { log.Fatalf("Failed to open key.pem for writing: %v", err) - return } privBytes, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { diff --git a/go.mod b/go.mod index 7389307..525e13f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/xtls/reality -go 1.19 +go 1.20 require ( github.com/pires/go-proxyproto v0.6.2 diff --git a/handshake_client.go b/handshake_client.go index 4d07cb1..0c4b3c2 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -8,6 +8,7 @@ import ( "bytes" "context" "crypto" + "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" @@ -19,7 +20,6 @@ import ( "io" "net" "strings" - "sync/atomic" "time" ) @@ -36,7 +36,7 @@ type clientHandshakeState struct { var testingOnlyForceClientHelloSignatureAlgorithms []SignatureScheme -func (c *Conn) makeClientHello() (*clientHelloMsg, ecdheParameters, error) { +func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) { config := c.config if len(config.ServerName) == 0 && !config.InsecureSkipVerify { return nil, nil, errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config") @@ -125,7 +125,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, ecdheParameters, error) { hello.supportedSignatureAlgorithms = testingOnlyForceClientHelloSignatureAlgorithms } - var params ecdheParameters + var key *ecdh.PrivateKey if hello.supportedVersions[0] == VersionTLS13 { if hasAESGCMHardwareSupport { hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...) @@ -134,17 +134,17 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, ecdheParameters, error) { } curveID := config.curvePreferences()[0] - if _, ok := curveForCurveID(curveID); curveID != X25519 && !ok { + if _, ok := curveForCurveID(curveID); !ok { return nil, nil, errors.New("tls: CurvePreferences includes unsupported curve") } - params, err = generateECDHEParameters(config.rand(), curveID) + key, err = generateECDHEKey(config.rand(), curveID) if err != nil { return nil, nil, err } - hello.keyShares = []keyShare{{group: curveID, data: params.PublicKey()}} + hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}} } - return hello, params, nil + return hello, key, nil } func (c *Conn) clientHandshake(ctx context.Context) (err error) { @@ -156,7 +156,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { // need to be reset. c.didResume = false - hello, ecdheParams, err := c.makeClientHello() + hello, ecdheKey, err := c.makeClientHello() if err != nil { return err } @@ -214,7 +214,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { ctx: ctx, serverHello: serverHello, hello: hello, - ecdheParams: ecdheParams, + ecdheKey: ecdheKey, session: session, earlySecret: earlySecret, binderKey: binderKey, @@ -455,7 +455,7 @@ func (hs *clientHandshakeState) handshake() error { } c.ekm = ekmFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.hello.random, hs.serverHello.random) - atomic.StoreUint32(&c.handshakeStatus, 1) + c.isHandshakeComplete.Store(true) return nil } @@ -629,7 +629,7 @@ func (hs *clientHandshakeState) doFullHandshake() error { } } - signed := hs.finishedHash.hashForClientCertificate(sigType, sigHash, hs.masterSecret) + signed := hs.finishedHash.hashForClientCertificate(sigType, sigHash) signOpts := crypto.SignerOpts(sigHash) if sigType == signatureRSAPSS { signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash} @@ -849,14 +849,16 @@ func (hs *clientHandshakeState) sendFinished(out []byte) error { // verifyServerCertificate parses and verifies the provided chain, setting // c.verifiedChains and c.peerCertificates or sending the appropriate alert. func (c *Conn) verifyServerCertificate(certificates [][]byte) error { + activeHandles := make([]*activeCert, len(certificates)) certs := make([]*x509.Certificate, len(certificates)) for i, asn1Data := range certificates { - cert, err := x509.ParseCertificate(asn1Data) + cert, err := clientCertCache.newCert(asn1Data) if err != nil { c.sendAlert(alertBadCertificate) return errors.New("tls: failed to parse certificate from server: " + err.Error()) } - certs[i] = cert + activeHandles[i] = cert + certs[i] = cert.cert } if !c.config.InsecureSkipVerify { @@ -874,7 +876,7 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { c.verifiedChains, err = certs[0].Verify(opts) if err != nil { c.sendAlert(alertBadCertificate) - return err + return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} } } @@ -886,6 +888,7 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { return fmt.Errorf("tls: server's certificate contains an unsupported type of public key: %T", certs[0].PublicKey) } + c.activeCertHandles = activeHandles c.peerCertificates = certs if c.config.VerifyPeerCertificate != nil { diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index cda4555..7a0b5ce 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -8,11 +8,11 @@ import ( "bytes" "context" "crypto" + "crypto/ecdh" "crypto/hmac" "crypto/rsa" "errors" "hash" - "sync/atomic" "time" ) @@ -21,7 +21,7 @@ type clientHandshakeStateTLS13 struct { ctx context.Context serverHello *serverHelloMsg hello *clientHelloMsg - ecdheParams ecdheParameters + ecdheKey *ecdh.PrivateKey session *ClientSessionState earlySecret []byte @@ -36,7 +36,7 @@ type clientHandshakeStateTLS13 struct { trafficSecret []byte // client_application_traffic_secret_0 } -// handshake requires hs.c, hs.hello, hs.serverHello, hs.ecdheParams, and, +// handshake requires hs.c, hs.hello, hs.serverHello, hs.ecdheKey, and, // optionally, hs.session, hs.earlySecret and hs.binderKey to be set. func (hs *clientHandshakeStateTLS13) handshake() error { c := hs.c @@ -53,7 +53,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error { } // Consistency check on the presence of a keyShare and its parameters. - if hs.ecdheParams == nil || len(hs.hello.keyShares) != 1 { + if hs.ecdheKey == nil || len(hs.hello.keyShares) != 1 { return c.sendAlert(alertInternalError) } @@ -104,7 +104,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error { return err } - atomic.StoreUint32(&c.handshakeStatus, 1) + c.isHandshakeComplete.Store(true) return nil } @@ -222,21 +222,21 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { c.sendAlert(alertIllegalParameter) return errors.New("tls: server selected unsupported group") } - if hs.ecdheParams.CurveID() == curveID { + 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); curveID != X25519 && !ok { + if _, ok := curveForCurveID(curveID); !ok { c.sendAlert(alertInternalError) return errors.New("tls: CurvePreferences includes unsupported curve") } - params, err := generateECDHEParameters(c.config.rand(), curveID) + key, err := generateECDHEKey(c.config.rand(), curveID) if err != nil { c.sendAlert(alertInternalError) return err } - hs.ecdheParams = params - hs.hello.keyShares = []keyShare{{group: curveID, data: params.PublicKey()}} + hs.ecdheKey = key + hs.hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}} } hs.hello.raw = nil @@ -310,7 +310,7 @@ func (hs *clientHandshakeStateTLS13) processServerHello() error { c.sendAlert(alertIllegalParameter) return errors.New("tls: server did not send a key share") } - if hs.serverHello.serverShare.group != hs.ecdheParams.CurveID() { + if sentID, _ := curveIDForCurve(hs.ecdheKey.Curve()); hs.serverHello.serverShare.group != sentID { c.sendAlert(alertIllegalParameter) return errors.New("tls: server selected unsupported group") } @@ -348,8 +348,13 @@ func (hs *clientHandshakeStateTLS13) processServerHello() error { func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { c := hs.c - sharedKey := hs.ecdheParams.SharedKey(hs.serverHello.serverShare.data) - if sharedKey == 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") } @@ -368,7 +373,7 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { serverHandshakeTrafficLabel, hs.transcript) c.in.setTrafficSecret(hs.suite, serverSecret) - err := c.config.writeKeyLog(keyLogLabelClientHandshake, hs.hello.random, clientSecret) + err = c.config.writeKeyLog(keyLogLabelClientHandshake, hs.hello.random, clientSecret) if err != nil { c.sendAlert(alertInternalError) return err diff --git a/handshake_server.go b/handshake_server.go index 3b079f2..5d21902 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -16,7 +16,6 @@ import ( "fmt" "hash" "io" - "sync/atomic" "time" ) @@ -122,7 +121,7 @@ func (hs *serverHandshakeState) handshake() error { } c.ekm = ekmFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.clientHello.random, hs.hello.random) - atomic.StoreUint32(&c.handshakeStatus, 1) + c.isHandshakeComplete.Store(true) return nil } @@ -668,7 +667,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { } } - signed := hs.finishedHash.hashForClientCertificate(sigType, sigHash, hs.masterSecret) + signed := hs.finishedHash.hashForClientCertificate(sigType, sigHash) if err := verifyHandshakeSignature(sigType, pub, sigHash, signed, certVerify.signature); err != nil { c.sendAlert(alertDecryptError) return errors.New("tls: invalid signature by the client certificate: " + err.Error()) @@ -832,7 +831,7 @@ func (c *Conn) processCertsFromClient(certificate Certificate) error { chains, err := certs[0].Verify(opts) if err != nil { c.sendAlert(alertBadCertificate) - return errors.New("tls: failed to verify client certificate: " + err.Error()) + return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} } c.verifiedChains = chains diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 4f39c71..d1a4f2e 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -140,7 +140,7 @@ func (hs *serverHandshakeStateTLS13) handshake() error { return err } - atomic.StoreUint32(&c.handshakeStatus, 1) + c.isHandshakeComplete.Store(true) return nil } @@ -264,18 +264,23 @@ GroupSelection: clientKeyShare = &hs.clientHello.keyShares[0] } - if _, ok := curveForCurveID(selectedGroup); selectedGroup != X25519 && !ok { + if _, ok := curveForCurveID(selectedGroup); !ok { c.sendAlert(alertInternalError) return errors.New("tls: CurvePreferences includes unsupported curve") } - params, err := generateECDHEParameters(c.config.rand(), selectedGroup) + key, err := generateECDHEKey(c.config.rand(), selectedGroup) if err != nil { c.sendAlert(alertInternalError) return err } - hs.hello.serverShare = keyShare{group: selectedGroup, data: params.PublicKey()} - hs.sharedKey = params.SharedKey(clientKeyShare.data) - if hs.sharedKey == nil { + 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 { c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid client key share") } diff --git a/key_agreement.go b/key_agreement.go index 4c555cf..d052b9e 100644 --- a/key_agreement.go +++ b/key_agreement.go @@ -6,6 +6,7 @@ package reality import ( "crypto" + "crypto/ecdh" "crypto/md5" "crypto/rsa" "crypto/sha1" @@ -157,7 +158,7 @@ func hashForServerKeyExchange(sigType uint8, hashFunc crypto.Hash, version uint1 type ecdheKeyAgreement struct { version uint16 isRSA bool - params ecdheParameters + key *ecdh.PrivateKey // ckx and preMasterSecret are generated in processServerKeyExchange // and returned in generateClientKeyExchange. @@ -177,18 +178,18 @@ func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Cer if curveID == 0 { return nil, errors.New("tls: no supported elliptic curves offered") } - if _, ok := curveForCurveID(curveID); curveID != X25519 && !ok { + if _, ok := curveForCurveID(curveID); !ok { return nil, errors.New("tls: CurvePreferences includes unsupported curve") } - params, err := generateECDHEParameters(config.rand(), curveID) + key, err := generateECDHEKey(config.rand(), curveID) if err != nil { return nil, err } - ka.params = params + ka.key = key // See RFC 4492, Section 5.4. - ecdhePublic := params.PublicKey() + ecdhePublic := key.PublicKey().Bytes() serverECDHEParams := make([]byte, 1+2+1+len(ecdhePublic)) serverECDHEParams[0] = 3 // named curve serverECDHEParams[1] = byte(curveID >> 8) @@ -259,8 +260,12 @@ func (ka *ecdheKeyAgreement) processClientKeyExchange(config *Config, cert *Cert return nil, errClientKeyExchange } - preMasterSecret := ka.params.SharedKey(ckx.ciphertext[1:]) - if preMasterSecret == nil { + peerKey, err := ka.key.Curve().NewPublicKey(ckx.ciphertext[1:]) + if err != nil { + return nil, errClientKeyExchange + } + preMasterSecret, err := ka.key.ECDH(peerKey) + if err != nil { return nil, errClientKeyExchange } @@ -288,22 +293,26 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell return errServerKeyExchange } - if _, ok := curveForCurveID(curveID); curveID != X25519 && !ok { + if _, ok := curveForCurveID(curveID); !ok { return errors.New("tls: server selected unsupported curve") } - params, err := generateECDHEParameters(config.rand(), curveID) + key, err := generateECDHEKey(config.rand(), curveID) if err != nil { return err } - ka.params = params + ka.key = key - ka.preMasterSecret = params.SharedKey(publicKey) - if ka.preMasterSecret == nil { + peerKey, err := key.Curve().NewPublicKey(publicKey) + if err != nil { + return errServerKeyExchange + } + ka.preMasterSecret, err = key.ECDH(peerKey) + if err != nil { return errServerKeyExchange } - ourPublicKey := params.PublicKey() + ourPublicKey := key.PublicKey().Bytes() ka.ckx = new(clientKeyExchangeMsg) ka.ckx.ciphertext = make([]byte, 1+len(ourPublicKey)) ka.ckx.ciphertext[0] = byte(len(ourPublicKey)) diff --git a/key_schedule.go b/key_schedule.go index 1d428a1..edffbb7 100644 --- a/key_schedule.go +++ b/key_schedule.go @@ -5,15 +5,13 @@ package reality import ( - "crypto/elliptic" + "crypto/ecdh" "crypto/hmac" "errors" "hash" "io" - "math/big" "golang.org/x/crypto/cryptobyte" - "golang.org/x/crypto/curve25519" "golang.org/x/crypto/hkdf" ) @@ -101,99 +99,43 @@ func (c *cipherSuiteTLS13) exportKeyingMaterial(masterSecret []byte, transcript } } -// ecdheParameters implements Diffie-Hellman with either NIST curves or X25519, +// generateECDHEKey returns a PrivateKey that implements Diffie-Hellman // according to RFC 8446, Section 4.2.8.2. -type ecdheParameters interface { - CurveID() CurveID - PublicKey() []byte - SharedKey(peerPublicKey []byte) []byte -} - -func generateECDHEParameters(rand io.Reader, curveID CurveID) (ecdheParameters, error) { - if curveID == X25519 { - privateKey := make([]byte, curve25519.ScalarSize) - if _, err := io.ReadFull(rand, privateKey); err != nil { - return nil, err - } - publicKey, err := curve25519.X25519(privateKey, curve25519.Basepoint) - if err != nil { - return nil, err - } - return &x25519Parameters{privateKey: privateKey, publicKey: publicKey}, nil - } - +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") } - p := &nistParameters{curveID: curveID} - var err error - p.privateKey, p.x, p.y, err = elliptic.GenerateKey(curve, rand) - if err != nil { - return nil, err - } - return p, nil + return curve.GenerateKey(rand) } -func curveForCurveID(id CurveID) (elliptic.Curve, bool) { +func curveForCurveID(id CurveID) (ecdh.Curve, bool) { switch id { + case X25519: + return ecdh.X25519(), true case CurveP256: - return elliptic.P256(), true + return ecdh.P256(), true case CurveP384: - return elliptic.P384(), true + return ecdh.P384(), true case CurveP521: - return elliptic.P521(), true + return ecdh.P521(), true default: return nil, false } } -type nistParameters struct { - privateKey []byte - x, y *big.Int // public key - curveID CurveID -} - -func (p *nistParameters) CurveID() CurveID { - return p.curveID -} - -func (p *nistParameters) PublicKey() []byte { - curve, _ := curveForCurveID(p.curveID) - return elliptic.Marshal(curve, p.x, p.y) -} - -func (p *nistParameters) SharedKey(peerPublicKey []byte) []byte { - curve, _ := curveForCurveID(p.curveID) - // Unmarshal also checks whether the given point is on the curve. - x, y := elliptic.Unmarshal(curve, peerPublicKey) - if x == nil { - return nil +func curveIDForCurve(curve ecdh.Curve) (CurveID, bool) { + switch curve { + case ecdh.X25519(): + return X25519, true + case ecdh.P256(): + return CurveP256, true + case ecdh.P384(): + return CurveP384, true + case ecdh.P521(): + return CurveP521, true + default: + return 0, false } - - xShared, _ := curve.ScalarMult(x, y, p.privateKey) - sharedKey := make([]byte, (curve.Params().BitSize+7)/8) - return xShared.FillBytes(sharedKey) -} - -type x25519Parameters struct { - privateKey []byte - publicKey []byte -} - -func (p *x25519Parameters) CurveID() CurveID { - return X25519 -} - -func (p *x25519Parameters) PublicKey() []byte { - return p.publicKey[:] -} - -func (p *x25519Parameters) SharedKey(peerPublicKey []byte) []byte { - sharedKey, err := curve25519.X25519(p.privateKey, peerPublicKey) - if err != nil { - return nil - } - return sharedKey } diff --git a/prf.go b/prf.go index 7eea1de..f0dac30 100644 --- a/prf.go +++ b/prf.go @@ -215,7 +215,7 @@ func (h finishedHash) serverSum(masterSecret []byte) []byte { // hashForClientCertificate returns the handshake messages so far, pre-hashed if // necessary, suitable for signing by a TLS client certificate. -func (h finishedHash) hashForClientCertificate(sigType uint8, hashAlg crypto.Hash, masterSecret []byte) []byte { +func (h finishedHash) hashForClientCertificate(sigType uint8, hashAlg crypto.Hash) []byte { if (h.version >= VersionTLS12 || sigType == signatureEd25519) && h.buffer == nil { panic("tls: handshake hash for a client certificate requested after discarding the handshake buffer") } diff --git a/tls.go b/tls.go index ea02484..1e7af81 100644 --- a/tls.go +++ b/tls.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE-Go file. -// Server side implementation of REALITY protocol, a fork of package tls in Go 1.19.5. -// For client side, please follow https://github.com/XTLS/Xray-core. +// Server side implementation of REALITY protocol, a fork of package tls in Go 1.20. +// For client side, please follow https://github.com/XTLS/Xray-core/blob/main/transport/internet/reality/reality.go. package reality // BUG(agl): The crypto/tls package only implements some countermeasures