diff --git a/handshake_1.20.go b/handshake_1.20.go index ec0e61d..fb0661d 100644 --- a/handshake_1.20.go +++ b/handshake_1.20.go @@ -148,10 +148,10 @@ type clientHandshakeStateTLS13 struct { earlySecret []byte binderKey []byte - certReq *uintptr + certReq unsafe.Pointer usingPSK bool sentDummyCCS bool - suite *uintptr + suite unsafe.Pointer transcript hash.Hash masterSecret []byte trafficSecret []byte // client_application_traffic_secret_0 @@ -186,7 +186,7 @@ type clientHandshakeState struct { ctx context.Context serverHello *serverHelloMsg hello *clientHelloMsg - suite *uintptr + suite unsafe.Pointer finishedHash finishedHash masterSecret []byte session *sessionState // the session being resumed diff --git a/handshake.go b/handshake_1.21.go similarity index 99% rename from handshake.go rename to handshake_1.21.go index 6f6b5e7..dbb6830 100644 --- a/handshake.go +++ b/handshake_1.21.go @@ -1,4 +1,4 @@ -//go:build go1.21 +//go:build go1.21 && !go1.24 package terasu @@ -240,7 +240,7 @@ type clientHandshakeStateTLS13 struct { earlySecret []byte binderKey []byte - certReq *uintptr + certReq unsafe.Pointer usingPSK bool sentDummyCCS bool suite *cipherSuiteTLS13 @@ -278,7 +278,7 @@ type clientHandshakeState struct { ctx context.Context serverHello *serverHelloMsg hello *clientHelloMsg - suite *uintptr + suite unsafe.Pointer finishedHash finishedHash masterSecret []byte session *sessionState // the session being resumed diff --git a/handshake_1.24.go b/handshake_1.24.go new file mode 100644 index 0000000..8fadb07 --- /dev/null +++ b/handshake_1.24.go @@ -0,0 +1,609 @@ +//go:build go1.24 + +package terasu + +import ( + "context" + "crypto" + "crypto/cipher" + "crypto/ecdh" + "crypto/mlkem" + "crypto/tls" + "crypto/x509" + "errors" + "hash" + "io" + "unsafe" +) + +//go:linkname defaultConfig crypto/tls.defaultConfig +func defaultConfig() *tls.Config + +// TLS 1.3 PSK Identity. Can be a Session Ticket, or a reference to a saved +// session. See RFC 8446, Section 4.2.11. +type pskIdentity struct { + label []byte + obfuscatedTicketAge uint32 +} + +type clientHelloMsg struct { + original []byte + vers uint16 + random []byte + sessionId []byte + cipherSuites []uint16 + compressionMethods []uint8 + serverName string + ocspStapling bool + supportedCurves []tls.CurveID + supportedPoints []uint8 + ticketSupported bool + sessionTicket []uint8 + supportedSignatureAlgorithms []tls.SignatureScheme + supportedSignatureAlgorithmsCert []tls.SignatureScheme + secureRenegotiationSupported bool + secureRenegotiation []byte + extendedMasterSecret bool + alpnProtocols []string + scts bool + supportedVersions []uint16 + cookie []byte + keyShares []byte + earlyData bool + pskModes []uint8 + pskIdentities []pskIdentity + pskBinders [][]byte + quicTransportParameters []byte + encryptedClientHello []byte + // extensions are only populated on the server-side of a handshake + extensions []uint16 +} + +//go:linkname marshal crypto/tls.(*clientHelloMsg).marshal +func marshal(m *clientHelloMsg) ([]byte, error) + +func (m *clientHelloMsg) marshal() ([]byte, error) { + return marshal(m) +} + +//go:linkname unmarshal crypto/tls.(*clientHelloMsg).unmarshal +func unmarshal(m *clientHelloMsg, data []byte) bool + +func (m *clientHelloMsg) unmarshal(data []byte) bool { + return unmarshal(m, data) +} + +//go:linkname clone crypto/tls.(*clientHelloMsg).clone +func clone(m *clientHelloMsg) *clientHelloMsg + +func (m *clientHelloMsg) clone() *clientHelloMsg { + return clone(m) +} + +type keySharePrivateKeys struct { + curveID tls.CurveID + ecdhe *ecdh.PrivateKey + mlkem *mlkem.DecapsulationKey768 +} + +type echCipher struct { + KDFID uint16 + AEADID uint16 +} + +type echExtension struct { + Type uint16 + Data []byte +} + +type echConfig struct { + raw []byte + + Version uint16 + Length uint16 + + ConfigID uint8 + KemID uint16 + PublicKey []byte + SymmetricCipherSuite []echCipher + + MaxNameLength uint8 + PublicName []byte + Extensions []echExtension +} + +type uint128 struct { + hi, lo uint64 +} + +type hpkecontext struct { + aead cipher.AEAD + + sharedSecret []byte + + suiteID []byte + + key []byte + baseNonce []byte + exporterSecret []byte + + seqNum uint128 +} + +type hpkeSender struct { + *hpkecontext +} + +type echClientContext struct { + config *echConfig + hpkeContext *hpkeSender + encapsulatedKey []byte + innerHello *clientHelloMsg + innerTranscript hash.Hash + kdfID uint16 + aeadID uint16 + echRejected bool + retryConfigs []byte +} + +//go:linkname makeClientHello crypto/tls.(*Conn).makeClientHello +func makeClientHello(c *_trsconn) (*clientHelloMsg, *keySharePrivateKeys, *echClientContext, error) + +func (c *_trsconn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echClientContext, error) { + return makeClientHello(c) +} + +// 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 +} + +// A sessionState is a resumable session. +type sessionState struct { + // Encoded as a SessionState (in the language of RFC 8446, Section 3). + // + // enum { server(1), client(2) } SessionStateType; + // + // opaque Certificate<1..2^24-1>; + // + // Certificate CertificateChain<0..2^24-1>; + // + // opaque Extra<0..2^24-1>; + // + // struct { + // uint16 version; + // SessionStateType type; + // uint16 cipher_suite; + // uint64 created_at; + // opaque secret<1..2^8-1>; + // Extra extra<0..2^24-1>; + // uint8 ext_master_secret = { 0, 1 }; + // uint8 early_data = { 0, 1 }; + // CertificateEntry certificate_list<0..2^24-1>; + // CertificateChain verified_chains<0..2^24-1>; /* excluding leaf */ + // select (SessionState.early_data) { + // case 0: Empty; + // case 1: opaque alpn<1..2^8-1>; + // }; + // select (SessionState.type) { + // case server: Empty; + // case client: struct { + // select (SessionState.version) { + // case VersionTLS10..VersionTLS12: Empty; + // case VersionTLS13: struct { + // uint64 use_by; + // uint32 age_add; + // }; + // }; + // }; + // }; + // } SessionState; + // + + // Extra is ignored by crypto/tls, but is encoded by [SessionState.Bytes] + // and parsed by [ParseSessionState]. + // + // This allows [Config.UnwrapSession]/[Config.WrapSession] and + // [ClientSessionCache] implementations to store and retrieve additional + // data alongside this session. + // + // To allow different layers in a protocol stack to share this field, + // applications must only append to it, not replace it, and must use entries + // that can be recognized even if out of order (for example, by starting + // with an id and version prefix). + Extra [][]byte + + // EarlyData indicates whether the ticket can be used for 0-RTT in a QUIC + // connection. The application may set this to false if it is true to + // decline to offer 0-RTT even if supported. + EarlyData bool + + version uint16 + isClient bool + cipherSuite uint16 + // createdAt is the generation time of the secret on the sever (which for + // TLS 1.0–1.2 might be earlier than the current session) and the time at + // which the ticket was received on the client. + createdAt uint64 // seconds since UNIX epoch + secret []byte // master secret for TLS 1.2, or the PSK for TLS 1.3 + extMasterSecret bool + peerCertificates []*x509.Certificate + activeCertHandles []*activeCert + ocspResponse []byte + scts [][]byte + verifiedChains [][]*x509.Certificate + alpnProtocol string // only set if EarlyData is true + + // Client-side TLS 1.3-only fields. + useBy uint64 // seconds since UNIX epoch + ageAdd uint32 + ticket []byte +} + +type earlySecret struct { + secret []byte + hash func() any +} + +//go:linkname clientEarlyTrafficSecret crypto/internal/fips140/tls13.(*EarlySecret).ClientEarlyTrafficSecret +func clientEarlyTrafficSecret(s *earlySecret, transcript any) []byte + +//go:linkname loadSession crypto/tls.(*Conn).loadSession +func loadSession(c *_trsconn, hello *clientHelloMsg) ( + session *sessionState, earlySecret *earlySecret, binderKey []byte, err error, +) + +func (c *_trsconn) loadSession(hello *clientHelloMsg) ( + session *sessionState, earlySecret *earlySecret, binderKey []byte, err error, +) { + return loadSession(c, hello) +} + +//go:linkname clientSessionCacheKey crypto/tls.(*Conn).clientSessionCacheKey +func clientSessionCacheKey(c *_trsconn) string + +func (c *_trsconn) clientSessionCacheKey() string { + return clientSessionCacheKey(c) +} + +// A cipherSuiteTLS13 defines only the pair of the AEAD algorithm and hash +// algorithm to be used with HKDF. See RFC 8446, Appendix B.4. +type cipherSuiteTLS13 struct { + id uint16 + keyLen int + aead func(key, fixedNonce []byte) any + hash crypto.Hash +} + +//go:linkname deriveSecret crypto/tls.(*cipherSuiteTLS13).deriveSecret +func deriveSecret(c *cipherSuiteTLS13, secret []byte, label string, transcript hash.Hash) []byte + +func (c *cipherSuiteTLS13) deriveSecret(secret []byte, label string, transcript hash.Hash) []byte { + return deriveSecret(c, secret, label, transcript) +} + +//go:linkname cipherSuiteTLS13ByID crypto/tls.cipherSuiteTLS13ByID +func cipherSuiteTLS13ByID(id uint16) *cipherSuiteTLS13 + +type handshakeMessage interface { + marshal() ([]byte, error) + unmarshal([]byte) bool +} + +type transcriptHash interface { + Write([]byte) (int, error) +} + +//go:linkname transcriptMsg crypto/tls.transcriptMsg +func transcriptMsg(msg handshakeMessage, h transcriptHash) error + +const clientEarlyTrafficLabel = "c e traffic" + +//go:linkname quicSetWriteSecret crypto/tls.(*Conn).quicSetWriteSecret +func quicSetWriteSecret(c *_trsconn, level tls.QUICEncryptionLevel, suite uint16, secret []byte) + +//go:linkname readHandshake crypto/tls.(*Conn).readHandshake +func readHandshake(c *_trsconn, transcript transcriptHash) (any, error) + +func (c *_trsconn) readHandshake(transcript transcriptHash) (any, error) { + return readHandshake(c, transcript) +} + +// TLS 1.3 Key Share. See RFC 8446, Section 4.2.8. +type keyShare struct { + group tls.CurveID + data []byte +} + +type serverHelloMsg struct { + original []byte + vers uint16 + random []byte + sessionId []byte + cipherSuite uint16 + compressionMethod uint8 + ocspStapling bool + ticketSupported bool + secureRenegotiationSupported bool + secureRenegotiation []byte + extendedMasterSecret bool + alpnProtocol string + scts [][]byte + supportedVersion uint16 + serverShare keyShare + selectedIdentityPresent bool + selectedIdentity uint16 + supportedPoints []uint8 + encryptedClientHello []byte + serverNameAck bool + + // HelloRetryRequest extensions + cookie []byte + selectedGroup tls.CurveID +} + +//go:linkname sendAlert crypto/tls.(*Conn).sendAlert +func sendAlert(c *_trsconn, err alert) error + +func (c *_trsconn) sendAlert(err alert) error { + return sendAlert(c, err) +} + +//go:linkname unexpectedMessageError crypto/tls.unexpectedMessageError +func unexpectedMessageError(wanted, got any) error + +const ( + alertUnexpectedMessage alert = 10 + alertIllegalParameter alert = 47 +) + +//go:linkname pickTLSVersion crypto/tls.(*Conn).pickTLSVersion +func pickTLSVersion(c *_trsconn, serverHello *serverHelloMsg) error + +func (c *_trsconn) pickTLSVersion(serverHello *serverHelloMsg) error { + return pickTLSVersion(c, serverHello) +} + +//go:linkname maxSupportedVersion crypto/tls.(*Config).maxSupportedVersion +func maxSupportedVersion(c *tls.Config, isClient bool) uint16 + +const roleClient = true + +const ( + // downgradeCanaryTLS12 or downgradeCanaryTLS11 is embedded in the server + // random as a downgrade protection if the server would be capable of + // negotiating a higher version. See RFC 8446, Section 4.1.3. + downgradeCanaryTLS12 = "DOWNGRD\x01" + downgradeCanaryTLS11 = "DOWNGRD\x00" +) + +type clientHandshakeStateTLS13 struct { + c *Conn + ctx context.Context + serverHello *serverHelloMsg + hello *clientHelloMsg + keyShareKeys *keySharePrivateKeys + + session *sessionState + earlySecret *earlySecret + binderKey []byte + + certReq unsafe.Pointer + usingPSK bool + sentDummyCCS bool + suite *cipherSuiteTLS13 + transcript hash.Hash + masterSecret unsafe.Pointer + trafficSecret []byte // client_application_traffic_secret_0 + + echContext *echClientContext +} + +//go:linkname handshake13 crypto/tls.(*clientHandshakeStateTLS13).handshake +func handshake13(hs *clientHandshakeStateTLS13) error + +func (hs *clientHandshakeStateTLS13) handshake() error { + return handshake13(hs) +} + +type prfFunc func(secret []byte, label string, seed []byte, keyLen int) []byte + +// A finishedHash calculates the hash of a set of handshake messages suitable +// for including in a Finished message. +type finishedHash struct { + client hash.Hash + server hash.Hash + + // Prior to TLS 1.2, an additional MD5 hash is required. + clientMD5 hash.Hash + serverMD5 hash.Hash + + // In TLS 1.2, a full buffer is sadly required. + buffer []byte + + version uint16 + prf prfFunc +} + +type clientHandshakeState struct { + c *Conn + ctx context.Context + serverHello *serverHelloMsg + hello *clientHelloMsg + suite unsafe.Pointer + finishedHash finishedHash + masterSecret []byte + session *sessionState // the session being resumed + ticket []byte // a fresh ticket received during this handshake +} + +//go:linkname handshake crypto/tls.(*clientHandshakeState).handshake +func handshake(hs *clientHandshakeState) error + +func (hs *clientHandshakeState) handshake() error { + return handshake(hs) +} + +//go:linkname computeAndUpdateOuterECHExtension crypto/tls.computeAndUpdateOuterECHExtension +func computeAndUpdateOuterECHExtension(outer, inner *clientHelloMsg, ech *echClientContext, useKey bool) error + +// writeHandshakeRecord writes a handshake message to the connection and updates +// the record layer state. If transcript is non-nil the marshalled message is +// written to it. +func (c *_trsconn) writeHandshakeRecord(msg handshakeMessage, transcript transcriptHash, firstFragmentLen uint8) (int, error) { + c.out.Lock() + defer c.out.Unlock() + + data, err := msg.marshal() + if err != nil { + return 0, err + } + if transcript != nil { + transcript.Write(data) + } + + return c.writeRecordLocked(recordTypeHandshake, firstFragmentLen, data) +} + +func (cout *Conn) clientHandshake(firstFragmentLen uint8) func(context.Context) error { + return func(ctx context.Context) (err error) { + c := (*_trsconn)(unsafe.Pointer(cout)) + + if c.config == nil { + c.config = defaultConfig() + } + + // This may be a renegotiation handshake, in which case some fields + // need to be reset. + c.didResume = false + + hello, keyShareKeys, ech, err := c.makeClientHello() + if err != nil { + return err + } + c.serverName = hello.serverName + + session, earlySecret, binderKey, err := c.loadSession(hello) + if err != nil { + return err + } + if session != nil { + defer func() { + // If we got a handshake failure when resuming a session, throw away + // the session ticket. See RFC 5077, Section 3.2. + // + // RFC 8446 makes no mention of dropping tickets on failure, but it + // does require servers to abort on invalid binders, so we need to + // delete tickets to recover from a corrupted PSK. + if err != nil { + if cacheKey := c.clientSessionCacheKey(); cacheKey != "" { + c.config.ClientSessionCache.Put(cacheKey, nil) + } + } + }() + } + + if ech != nil { + // Split hello into inner and outer + ech.innerHello = hello.clone() + + // Overwrite the server name in the outer hello with the public facing + // name. + hello.serverName = string(ech.config.PublicName) + // Generate a new random for the outer hello. + hello.random = make([]byte, 32) + _, err = io.ReadFull(tlsConfigRand(c.config), hello.random) + if err != nil { + return errors.New("tls: short read from Rand: " + err.Error()) + } + + // NOTE: we don't do PSK GREASE, in line with boringssl, it's meant to + // work around _possibly_ broken middleboxes, but there is little-to-no + // evidence that this is actually a problem. + + if err := computeAndUpdateOuterECHExtension(hello, ech.innerHello, ech, true); err != nil { + return err + } + } + + c.serverName = hello.serverName + + if _, err := c.writeHandshakeRecord(hello, nil, firstFragmentLen); err != nil { + return err + } + + if hello.earlyData { + suite := cipherSuiteTLS13ByID(session.cipherSuite) + transcript := suite.hash.New() + if err := transcriptMsg(hello, transcript); err != nil { + return err + } + earlyTrafficSecret := clientEarlyTrafficSecret(earlySecret, transcript) + quicSetWriteSecret(c, tls.QUICEncryptionLevelEarly, suite.id, earlyTrafficSecret) + } + + // serverHelloMsg is not included in the transcript + msg, err := c.readHandshake(nil) + if err != nil { + return err + } + + var serverHello *serverHelloMsg + if !isTypeEqual(msg, "*tls.serverHelloMsg") { + c.sendAlert(alertUnexpectedMessage) + return unexpectedMessageError(serverHello, msg) + } + serverHello = (*serverHelloMsg)(*(*unsafe.Pointer)( + unsafe.Add(unsafe.Pointer(&msg), unsafe.Sizeof(uintptr(0))), + )) + + if err := c.pickTLSVersion(serverHello); err != nil { + return err + } + + // If we are negotiating a protocol version that's lower than what we + // support, check for the server downgrade canaries. + // See RFC 8446, Section 4.1.3. + maxVers := maxSupportedVersion(c.config, roleClient) + tls12Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS12 + tls11Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS11 + if maxVers == tls.VersionTLS13 && c.vers <= tls.VersionTLS12 && (tls12Downgrade || tls11Downgrade) || + maxVers == tls.VersionTLS12 && c.vers <= tls.VersionTLS11 && tls11Downgrade { + c.sendAlert(alertIllegalParameter) + return errors.New("tls: downgrade attempt detected, possibly due to a MitM attack or a broken middlebox") + } + + if c.vers == tls.VersionTLS13 { + hs := &clientHandshakeStateTLS13{ + c: cout, + ctx: ctx, + serverHello: serverHello, + hello: hello, + keyShareKeys: keyShareKeys, + session: session, + earlySecret: earlySecret, + binderKey: binderKey, + echContext: ech, + } + + // In TLS 1.3, session tickets are delivered after the handshake. + return hs.handshake() + } + + hs := &clientHandshakeState{ + c: cout, + ctx: ctx, + serverHello: serverHello, + hello: hello, + session: session, + } + + if err := hs.handshake(); err != nil { + return err + } + + return nil + } +} diff --git a/tls_1.20.go b/tls_1.20.go index 3f92d8b..3cc4f52 100644 --- a/tls_1.20.go +++ b/tls_1.20.go @@ -87,7 +87,7 @@ type _trsconn struct { peerCertificates []*x509.Certificate // activeCertHandles contains the cache handles to certificates in // peerCertificates that are used to track active references. - activeCertHandles []*uintptr + activeCertHandles []unsafe.Pointer // verifiedChains contains the certificate chains that we built, as // opposed to the ones presented by the server. verifiedChains [][]*x509.Certificate diff --git a/tls_1.21.go b/tls_1.21.go index d745a8e..2cb6b1f 100644 --- a/tls_1.21.go +++ b/tls_1.21.go @@ -66,7 +66,7 @@ type _trsconn struct { conn net.Conn isClient bool handshakeFn func(context.Context) error // (*Conn).clientHandshake or serverHandshake - quic *uintptr // nil for non-QUIC connections + quic unsafe.Pointer // nil for non-QUIC connections // isHandshakeComplete is true if the connection is currently transferring // application data (i.e. is not currently processing a handshake). @@ -90,7 +90,7 @@ type _trsconn struct { peerCertificates []*x509.Certificate // activeCertHandles contains the cache handles to certificates in // peerCertificates that are used to track active references. - activeCertHandles []*uintptr + activeCertHandles []unsafe.Pointer // verifiedChains contains the certificate chains that we built, as // opposed to the ones presented by the server. verifiedChains [][]*x509.Certificate diff --git a/tls_1.23.go b/tls_1.23.go index f140409..3c968cd 100644 --- a/tls_1.23.go +++ b/tls_1.23.go @@ -1,4 +1,4 @@ -//go:build go1.23 +//go:build go1.23 && !go1.24 package terasu @@ -66,7 +66,7 @@ type _trsconn struct { conn net.Conn isClient bool handshakeFn func(context.Context) error // (*Conn).clientHandshake or serverHandshake - quic *uintptr // nil for non-QUIC connections + quic unsafe.Pointer // nil for non-QUIC connections // isHandshakeComplete is true if the connection is currently transferring // application data (i.e. is not currently processing a handshake). @@ -92,7 +92,7 @@ type _trsconn struct { peerCertificates []*x509.Certificate // activeCertHandles contains the cache handles to certificates in // peerCertificates that are used to track active references. - activeCertHandles []*uintptr + activeCertHandles []unsafe.Pointer // verifiedChains contains the certificate chains that we built, as // opposed to the ones presented by the server. verifiedChains [][]*x509.Certificate diff --git a/tls_1.24.go b/tls_1.24.go new file mode 100644 index 0000000..6274e0d --- /dev/null +++ b/tls_1.24.go @@ -0,0 +1,277 @@ +//go:build go1.24 + +package terasu + +import ( + "context" + "crypto/tls" + "crypto/x509" + "hash" + "io" + "net" + "sync" + "sync/atomic" + "unsafe" + _ "unsafe" +) + +type recordType uint8 + +const ( + recordTypeChangeCipherSpec recordType = 20 + recordTypeAlert recordType = 21 + recordTypeHandshake recordType = 22 + recordTypeApplicationData recordType = 23 +) + +const ( + recordHeaderLen = 5 // record header length +) + +type alert uint8 + +//go:linkname tlsConfigRand crypto/tls.(*Config).rand +func tlsConfigRand(c *tls.Config) io.Reader + +//go:linkname alertError tls.(tls.alert).Error +func alertError(e alert) string + +func (e alert) Error() string { + return alertError(e) +} + +// A halfConn represents one direction of the record layer +// connection, either sending or receiving. +type halfConn struct { + sync.Mutex + + err error // first permanent error + version uint16 // protocol version + cipher any // cipher algorithm + mac hash.Hash + seq [8]byte // 64-bit sequence number + + scratchBuf [13]byte // to avoid allocs; interface method args escape + + nextCipher any // next encryption state + nextMac hash.Hash // next MAC algorithm + + level tls.QUICEncryptionLevel // current QUIC encryption level + trafficSecret []byte // current TLS 1.3 traffic secret +} + +type Conn tls.Conn + +// A _trsconn represents a secured connection. +// It implements the net._trsconn interface. +type _trsconn struct { + // constant + conn net.Conn + isClient bool + handshakeFn func(context.Context) error // (*Conn).clientHandshake or serverHandshake + quic unsafe.Pointer // nil for non-QUIC connections + + // isHandshakeComplete is true if the connection is currently transferring + // application data (i.e. is not currently processing a handshake). + // isHandshakeComplete is true implies handshakeErr == nil. + isHandshakeComplete atomic.Bool + // constant after handshake; protected by handshakeMutex + handshakeMutex sync.Mutex + handshakeErr error // error resulting from handshake + vers uint16 // TLS version + haveVers bool // version has been negotiated + config *tls.Config // configuration passed to constructor + // handshakes counts the number of handshakes performed on the + // connection so far. If renegotiation is disabled then this is either + // zero or one. + handshakes int + extMasterSecret bool + didResume bool // whether this connection was a session resumption + didHRR bool // whether a HelloRetryRequest was sent/received + cipherSuite uint16 + curveID tls.CurveID + 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 []unsafe.Pointer + // verifiedChains contains the certificate chains that we built, as + // opposed to the ones presented by the server. + verifiedChains [][]*x509.Certificate + // serverName contains the server name indicated by the client, if any. + serverName string + // secureRenegotiation is true if the server echoed the secure + // renegotiation extension. (This is meaningless as a server because + // renegotiation is not supported in that case.) + secureRenegotiation bool + // ekm is a closure for exporting keying material. + ekm func(label string, context []byte, length int) ([]byte, error) + // resumptionSecret is the resumption_master_secret for handling + // or sending NewSessionTicket messages. + resumptionSecret []byte + echAccepted bool + + // ticketKeys is the set of active session ticket keys for this + // connection. The first one is used to encrypt new tickets and + // all are tried to decrypt tickets. + ticketKeys []byte + + // clientFinishedIsFirst is true if the client sent the first Finished + // message during the most recent handshake. This is recorded because + // the first transmitted Finished message is the tls-unique + // channel-binding value. + clientFinishedIsFirst bool + + // closeNotifyErr is any error from sending the alertCloseNotify record. + closeNotifyErr error + // closeNotifySent is true if the Conn attempted to send an + // alertCloseNotify record. + closeNotifySent bool + + // clientFinished and serverFinished contain the Finished message sent + // by the client or server in the most recent handshake. This is + // retained to support the renegotiation extension and tls-unique + // channel-binding. + clientFinished [12]byte + serverFinished [12]byte + + // clientProtocol is the negotiated ALPN protocol. + clientProtocol string + + // input/output + in, out halfConn +} + +//go:linkname outBufPool crypto/tls.outBufPool +var outBufPool sync.Pool + +//go:linkname tlsWriteRecordLocked crypto/tls.(*Conn).writeRecordLocked +func tlsWriteRecordLocked(c *_trsconn, typ recordType, data []byte) (int, error) + +//go:linkname maxPayloadSizeForWrite crypto/tls.(*Conn).maxPayloadSizeForWrite +func maxPayloadSizeForWrite(c *_trsconn, typ recordType) int + +func (c *_trsconn) maxPayloadSizeForWrite(typ recordType) int { + return maxPayloadSizeForWrite(c, typ) +} + +//go:linkname sliceForAppend crypto/tls.sliceForAppend +func sliceForAppend(in []byte, n int) (head, tail []byte) + +//go:linkname encrypt crypto/tls.(*halfConn).encrypt +func encrypt(hc *halfConn, record, payload []byte, rand io.Reader) ([]byte, error) + +func (hc *halfConn) encrypt(record, payload []byte, rand io.Reader) ([]byte, error) { + return encrypt(hc, record, payload, rand) +} + +//go:linkname rand crypto/tls.(*Config).rand +func rand(c *tls.Config) io.Reader + +//go:linkname write crypto/tls.(*Conn).write +func write(c *_trsconn, data []byte) (int, error) + +func (c *_trsconn) write(data []byte) (int, error) { + return write(c, data) +} + +//go:linkname flush crypto/tls.(*Conn).flush +func flush(c *_trsconn) (int, error) + +func (c *_trsconn) flush() (int, error) { + return flush(c) +} + +//go:linkname changeCipherSpec crypto/tls.(*halfConn).changeCipherSpec +func changeCipherSpec(hc *halfConn) error + +func (hc *halfConn) changeCipherSpec() error { + return changeCipherSpec(hc) +} + +//go:linkname sendAlertLocked crypto/tls.(*Conn).sendAlertLocked +func sendAlertLocked(c *_trsconn, err alert) error + +func (c *_trsconn) sendAlertLocked(err alert) error { + return sendAlertLocked(c, err) +} + +// writeRecordLocked writes a TLS record with the given type and payload to the +// connection and updates the record layer state. +func (c *_trsconn) writeRecordLocked(typ recordType, firstFragmentLen uint8, data []byte) (int, error) { + if c.quic != nil { + return tlsWriteRecordLocked(c, typ, data) + } + + outBufPtr := outBufPool.Get().(*[]byte) + outBuf := *outBufPtr + defer func() { + // You might be tempted to simplify this by just passing &outBuf to Put, + // but that would make the local copy of the outBuf slice header escape + // to the heap, causing an allocation. Instead, we keep around the + // pointer to the slice header returned by Get, which is already on the + // heap, and overwrite and return that. + *outBufPtr = outBuf + outBufPool.Put(outBufPtr) + }() + + var n int + isFirstLoop := true + for len(data) > 0 { + m := len(data) + if !isFirstLoop { + if maxPayload := c.maxPayloadSizeForWrite(typ); m > maxPayload { + m = maxPayload + } + } else { + m = int(firstFragmentLen) + } + + _, outBuf = sliceForAppend(outBuf[:0], recordHeaderLen) + outBuf[0] = byte(typ) + vers := c.vers + if vers == 0 { + // Some TLS servers fail if the record version is + // greater than TLS 1.0 for the initial ClientHello. + vers = tls.VersionTLS10 + } else if vers == tls.VersionTLS13 { + // TLS 1.3 froze the record layer version to 1.2. + // See RFC 8446, Section 5.1. + vers = tls.VersionTLS12 + } + outBuf[1] = byte(vers >> 8) + outBuf[2] = byte(vers) + outBuf[3] = byte(m >> 8) + outBuf[4] = byte(m) + + var err error + outBuf, err = c.out.encrypt(outBuf, data[:m], rand(c.config)) + if err != nil { + return n, err + } + if _, err := c.write(outBuf); err != nil { + return n, err + } + n += m + data = data[m:] + if isFirstLoop { + isFirstLoop = false + if _, err := c.flush(); err != nil { + return n, err + } + } + } + + if typ == recordTypeChangeCipherSpec && c.vers != tls.VersionTLS13 { + if err := c.out.changeCipherSpec(); err != nil { + return n, c.sendAlertLocked(alert( + *(*uintptr)( + unsafe.Add(unsafe.Pointer(&err), unsafe.Sizeof(uintptr(0))), + ), + )) + } + } + + return n, nil +}