Skip to content

Commit

Permalink
crypto/tls: CF events
Browse files Browse the repository at this point in the history
crypto/tls: Add experimental intra-handshake timing support

Extend the experimental Event API with two new event instances:
one for client and server intra-handshake state machine durations,
respectively. Each event records elapsed timestamps (durations)
for relevant events during the course of a connection, such as
reading and writing handshake messages of interest. This will be
useful for recording intra-stack costs of TLS extensions such as
ECH and KEMTLS.

crypto/tls: Consolidate CF-specific logic

* Rename EXP_Event to CFEvent.
* Move CFEvent implementations to tls_cf.go.
* Add CFControl parameter to Config. This value will be used to
propagate Cloudflare-internal logic from the TLS configuration to HTTP
requests.
  • Loading branch information
Christopher Wood authored and cjpatton committed Aug 20, 2021
1 parent 3310478 commit 35271a3
Show file tree
Hide file tree
Showing 10 changed files with 340 additions and 10 deletions.
24 changes: 24 additions & 0 deletions src/crypto/tls/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,13 @@ type ConnectionState struct {
// RFC 7627, and https://mitls.org/pages/attacks/3SHAKE#channelbindings.
TLSUnique []byte

// CFControl is used to pass additional TLS configuration information to
// HTTP requests.
//
// NOTE: This feature is used to implement Cloudflare-internal features.
// This feature is unstable and applications MUST NOT depend on it.
CFControl interface{}

// ekm is a closure exposed via ExportKeyingMaterial.
ekm func(label string, context []byte, length int) ([]byte, error)
}
Expand Down Expand Up @@ -689,6 +696,21 @@ type Config struct {
// used for debugging.
KeyLogWriter io.Writer

// CFEventHandler, if set, is called by the client and server at various
// points during the handshake to handle specific events. This is used
// primarily for collecting metrics.
//
// NOTE: This feature is used to implement Cloudflare-internal features.
// This feature is unstable and applications MUST NOT depend on it.
CFEventHandler func(event CFEvent)

// CFControl is used to pass additional TLS configuration information to
// HTTP requests via ConnectionState.
//
// NOTE: This feature is used to implement Cloudflare-internal features.
// This feature is unstable and applications MUST NOT depend on it.
CFControl interface{}

// mutex protects sessionTicketKeys and autoSessionTicketKeys.
mutex sync.RWMutex
// sessionTicketKeys contains zero or more ticket keys. If set, it means the
Expand Down Expand Up @@ -778,6 +800,8 @@ func (c *Config) Clone() *Config {
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
Renegotiation: c.Renegotiation,
KeyLogWriter: c.KeyLogWriter,
CFEventHandler: c.CFEventHandler,
CFControl: c.CFControl,
sessionTicketKeys: c.sessionTicketKeys,
autoSessionTicketKeys: c.autoSessionTicketKeys,
}
Expand Down
8 changes: 8 additions & 0 deletions src/crypto/tls/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,7 @@ func (c *Conn) Close() error {
if err := c.conn.Close(); err != nil {
return err
}

return alertErr
}

Expand Down Expand Up @@ -1424,6 +1425,7 @@ func (c *Conn) connectionStateLocked() ConnectionState {
state.VerifiedChains = c.verifiedChains
state.SignedCertificateTimestamps = c.scts
state.OCSPResponse = c.ocspResponse
state.CFControl = c.config.CFControl
if !c.didResume && c.vers != VersionTLS13 {
if c.clientFinishedIsFirst {
state.TLSUnique = c.clientFinished[:]
Expand Down Expand Up @@ -1469,3 +1471,9 @@ func (c *Conn) VerifyHostname(host string) error {
func (c *Conn) handshakeComplete() bool {
return atomic.LoadUint32(&c.handshakeStatus) == 1
}

func (c *Conn) handleCFEvent(event CFEvent) {
if c.config.CFEventHandler != nil {
c.config.CFEventHandler(event)
}
}
19 changes: 12 additions & 7 deletions src/crypto/tls/handshake_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ func (c *Conn) clientHandshake() (err error) {
c.config = defaultConfig()
}

handshakeTimings := createTLS13ClientHandshakeTimingInfo(c.config.Time)

// This may be a renegotiation handshake, in which case some fields
// need to be reset.
c.didResume = false
Expand Down Expand Up @@ -170,6 +172,8 @@ func (c *Conn) clientHandshake() (err error) {
return err
}

handshakeTimings.WriteClientHello = handshakeTimings.elapsedTime()

msg, err := c.readHandshake()
if err != nil {
return err
Expand Down Expand Up @@ -199,13 +203,14 @@ func (c *Conn) clientHandshake() (err error) {

if c.vers == VersionTLS13 {
hs := &clientHandshakeStateTLS13{
c: c,
serverHello: serverHello,
hello: hello,
ecdheParams: ecdheParams,
session: session,
earlySecret: earlySecret,
binderKey: binderKey,
c: c,
serverHello: serverHello,
hello: hello,
ecdheParams: ecdheParams,
session: session,
earlySecret: earlySecret,
binderKey: binderKey,
handshakeTimings: handshakeTimings,
}

// In TLS 1.3, session tickets are delivered after the handshake.
Expand Down
21 changes: 21 additions & 0 deletions src/crypto/tls/handshake_client_tls13.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type clientHandshakeStateTLS13 struct {
transcript hash.Hash
masterSecret []byte
trafficSecret []byte // client_application_traffic_secret_0

handshakeTimings CFEventTLS13ClientHandshakeTimingInfo
}

// handshake requires hs.c, hs.hello, hs.serverHello, hs.ecdheParams, and,
Expand Down Expand Up @@ -98,6 +100,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
return err
}

c.handleCFEvent(hs.handshakeTimings)
atomic.StoreUint32(&c.handshakeStatus, 1)

return nil
Expand Down Expand Up @@ -285,6 +288,10 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
func (hs *clientHandshakeStateTLS13) processServerHello() error {
c := hs.c

defer func() {
hs.handshakeTimings.ProcessServerHello = hs.handshakeTimings.elapsedTime()
}()

if bytes.Equal(hs.serverHello.random, helloRetryRequestRandom) {
c.sendAlert(alertUnexpectedMessage)
return errors.New("tls: server sent two HelloRetryRequest messages")
Expand Down Expand Up @@ -406,6 +413,8 @@ func (hs *clientHandshakeStateTLS13) readServerParameters() error {
c.clientProtocol = encryptedExtensions.alpnProtocol
}

hs.handshakeTimings.ReadEncryptedExtensions = hs.handshakeTimings.elapsedTime()

return nil
}

Expand Down Expand Up @@ -455,6 +464,8 @@ func (hs *clientHandshakeStateTLS13) readServerCertificate() error {
}
hs.transcript.Write(certMsg.marshal())

hs.handshakeTimings.ReadCertificate = hs.handshakeTimings.elapsedTime()

c.scts = certMsg.certificate.SignedCertificateTimestamps
c.ocspResponse = certMsg.certificate.OCSPStaple

Expand Down Expand Up @@ -495,6 +506,8 @@ func (hs *clientHandshakeStateTLS13) readServerCertificate() error {

hs.transcript.Write(certVerify.marshal())

hs.handshakeTimings.ReadCertificateVerify = hs.handshakeTimings.elapsedTime()

return nil
}

Expand All @@ -512,6 +525,8 @@ func (hs *clientHandshakeStateTLS13) readServerFinished() error {
return unexpectedMessageError(finished, msg)
}

hs.handshakeTimings.ReadServerFinished = hs.handshakeTimings.elapsedTime()

expectedMAC := hs.suite.finishedHash(c.in.trafficSecret, hs.transcript)
if !hmac.Equal(expectedMAC, finished.verifyData) {
c.sendAlert(alertDecryptError)
Expand Down Expand Up @@ -571,6 +586,8 @@ func (hs *clientHandshakeStateTLS13) sendClientCertificate() error {
return err
}

hs.handshakeTimings.WriteCertificate = hs.handshakeTimings.elapsedTime()

// If we sent an empty certificate message, skip the CertificateVerify.
if len(cert.Certificate) == 0 {
return nil
Expand Down Expand Up @@ -609,6 +626,8 @@ func (hs *clientHandshakeStateTLS13) sendClientCertificate() error {
return err
}

hs.handshakeTimings.WriteCertificateVerify = hs.handshakeTimings.elapsedTime()

return nil
}

Expand All @@ -624,6 +643,8 @@ func (hs *clientHandshakeStateTLS13) sendClientFinished() error {
return err
}

hs.handshakeTimings.WriteClientFinished = hs.handshakeTimings.elapsedTime()

c.out.setTrafficSecret(hs.suite, hs.trafficSecret)

if !c.config.SessionTicketsDisabled && c.config.ClientSessionCache != nil {
Expand Down
5 changes: 3 additions & 2 deletions src/crypto/tls/handshake_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ func (c *Conn) serverHandshake() error {

if c.vers == VersionTLS13 {
hs := serverHandshakeStateTLS13{
c: c,
clientHello: clientHello,
c: c,
clientHello: clientHello,
handshakeTimings: createTLS13ServerHandshakeTimingInfo(c.config.Time),
}
return hs.handshake()
}
Expand Down
22 changes: 22 additions & 0 deletions src/crypto/tls/handshake_server_tls13.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ type serverHandshakeStateTLS13 struct {
trafficSecret []byte // client_application_traffic_secret_0
transcript hash.Hash
clientFinished []byte

handshakeTimings CFEventTLS13ServerHandshakeTimingInfo
}

func (hs *serverHandshakeStateTLS13) handshake() error {
Expand Down Expand Up @@ -75,6 +77,7 @@ func (hs *serverHandshakeStateTLS13) handshake() error {
return err
}

c.handleCFEvent(hs.handshakeTimings)
atomic.StoreUint32(&c.handshakeStatus, 1)

return nil
Expand Down Expand Up @@ -233,6 +236,9 @@ GroupSelection:
}

c.serverName = hs.clientHello.serverName

hs.handshakeTimings.ProcessClientHello = hs.handshakeTimings.elapsedTime()

return nil
}

Expand Down Expand Up @@ -534,6 +540,8 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error {
return err
}

hs.handshakeTimings.WriteServerHello = hs.handshakeTimings.elapsedTime()

if err := hs.sendDummyChangeCipherSpec(); err != nil {
return err
}
Expand Down Expand Up @@ -577,6 +585,8 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error {
return err
}

hs.handshakeTimings.WriteEncryptedExtensions = hs.handshakeTimings.elapsedTime()

return nil
}

Expand Down Expand Up @@ -619,6 +629,8 @@ func (hs *serverHandshakeStateTLS13) sendServerCertificate() error {
return err
}

hs.handshakeTimings.WriteCertificate = hs.handshakeTimings.elapsedTime()

certVerifyMsg := new(certificateVerifyMsg)
certVerifyMsg.hasSignatureAlgorithm = true
certVerifyMsg.signatureAlgorithm = hs.sigAlg
Expand Down Expand Up @@ -651,6 +663,8 @@ func (hs *serverHandshakeStateTLS13) sendServerCertificate() error {
return err
}

hs.handshakeTimings.WriteCertificateVerify = hs.handshakeTimings.elapsedTime()

return nil
}

Expand All @@ -666,6 +680,8 @@ func (hs *serverHandshakeStateTLS13) sendServerFinished() error {
return err
}

hs.handshakeTimings.WriteServerFinished = hs.handshakeTimings.elapsedTime()

// Derive secrets that take context through the server Finished.

hs.masterSecret = hs.suite.extract(nil,
Expand Down Expand Up @@ -803,6 +819,8 @@ func (hs *serverHandshakeStateTLS13) readClientCertificate() error {
}
}

hs.handshakeTimings.ReadCertificate = hs.handshakeTimings.elapsedTime()

if len(certMsg.certificate.Certificate) != 0 {
msg, err = c.readHandshake()
if err != nil {
Expand Down Expand Up @@ -838,6 +856,8 @@ func (hs *serverHandshakeStateTLS13) readClientCertificate() error {
hs.transcript.Write(certVerify.marshal())
}

hs.handshakeTimings.ReadCertificateVerify = hs.handshakeTimings.elapsedTime()

// If we waited until the client certificates to send session tickets, we
// are ready to do it now.
if err := hs.sendSessionTickets(); err != nil {
Expand Down Expand Up @@ -866,6 +886,8 @@ func (hs *serverHandshakeStateTLS13) readClientFinished() error {
return errors.New("tls: invalid client finished hash")
}

hs.handshakeTimings.ReadClientFinished = hs.handshakeTimings.elapsedTime()

c.in.setTrafficSecret(hs.suite, hs.trafficSecret)

return nil
Expand Down
Loading

0 comments on commit 35271a3

Please sign in to comment.