Add support for sending TLS 1.3 tickets in Go.
Also parse out the ticket lifetime which was previously ignored.
BUG=75
Change-Id: I6ba92017bd4f1b31da55fd85d2af529fd592de11
Reviewed-on: https://boringssl-review.googlesource.com/8871
Reviewed-by: Nick Harper <nharper@chromium.org>
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 240a7ec..fd95781 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -189,6 +189,13 @@
SRTP_AES128_CM_HMAC_SHA1_32 = 0x0002
)
+// TicketFlags values (see draft-ietf-tls-tls13-14, section 4.4.1)
+const (
+ ticketAllowEarlyData = 1
+ ticketAllowDHEResumption = 2
+ ticketAllowPSKResumption = 4
+)
+
// ConnectionState records basic TLS details about the connection.
type ConnectionState struct {
Version uint16 // TLS version used by the connection (e.g. VersionTLS12)
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 1b6c557..8658fd2 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -59,6 +59,7 @@
clientRandom, serverRandom [32]byte
exporterSecret []byte
+ resumptionSecret []byte
clientProtocol string
clientProtocolFallback bool
@@ -1143,7 +1144,9 @@
case typeHelloRetryRequest:
m = new(helloRetryRequestMsg)
case typeNewSessionTicket:
- m = new(newSessionTicketMsg)
+ m = &newSessionTicketMsg{
+ version: c.vers,
+ }
case typeEncryptedExtensions:
m = new(encryptedExtensionsMsg)
case typeCertificate:
@@ -1582,3 +1585,39 @@
}
return false
}
+
+func (c *Conn) SendNewSessionTicket() error {
+ if c.isClient || c.vers < VersionTLS13 {
+ return errors.New("tls: cannot send post-handshake NewSessionTicket")
+ }
+
+ var peerCertificatesRaw [][]byte
+ for _, cert := range c.peerCertificates {
+ peerCertificatesRaw = append(peerCertificatesRaw, cert.Raw)
+ }
+ state := sessionState{
+ vers: c.vers,
+ cipherSuite: c.cipherSuite.id,
+ masterSecret: c.resumptionSecret,
+ certificates: peerCertificatesRaw,
+ }
+
+ // TODO(davidben): Allow configuring these values.
+ m := &newSessionTicketMsg{
+ version: c.vers,
+ ticketLifetime: uint32(24 * time.Hour / time.Second),
+ ticketFlags: ticketAllowDHEResumption | ticketAllowPSKResumption,
+ }
+ if !c.config.Bugs.SendEmptySessionTicket {
+ var err error
+ m.ticket, err = c.encryptTicket(&state)
+ if err != nil {
+ return err
+ }
+ }
+
+ c.out.Lock()
+ defer c.out.Unlock()
+ _, err := c.writeRecord(recordTypeHandshake, m.marshal())
+ return err
+}
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 15bafa0..41a8fb2 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -1715,50 +1715,75 @@
}
type newSessionTicketMsg struct {
- raw []byte
- ticket []byte
+ raw []byte
+ version uint16
+ ticketLifetime uint32
+ ticketFlags uint32
+ ticketAgeAdd uint32
+ ticket []byte
}
-func (m *newSessionTicketMsg) marshal() (x []byte) {
+func (m *newSessionTicketMsg) marshal() []byte {
if m.raw != nil {
return m.raw
}
// See http://tools.ietf.org/html/rfc5077#section-3.3
- ticketLen := len(m.ticket)
- length := 2 + 4 + ticketLen
- x = make([]byte, 4+length)
- x[0] = typeNewSessionTicket
- x[1] = uint8(length >> 16)
- x[2] = uint8(length >> 8)
- x[3] = uint8(length)
- x[8] = uint8(ticketLen >> 8)
- x[9] = uint8(ticketLen)
- copy(x[10:], m.ticket)
+ ticketMsg := newByteBuilder()
+ ticketMsg.addU8(typeNewSessionTicket)
+ body := ticketMsg.addU24LengthPrefixed()
+ body.addU32(m.ticketLifetime)
+ if m.version >= VersionTLS13 {
+ body.addU32(m.ticketFlags)
+ body.addU32(m.ticketAgeAdd)
+ // Send no extensions.
+ //
+ // TODO(davidben): Add an option to send a custom extension to
+ // test we correctly ignore unknown ones.
+ body.addU16(0)
+ }
+ ticket := body.addU16LengthPrefixed()
+ ticket.addBytes(m.ticket)
- m.raw = x
-
- return
+ m.raw = ticketMsg.finish()
+ return m.raw
}
func (m *newSessionTicketMsg) unmarshal(data []byte) bool {
m.raw = data
- if len(data) < 10 {
+ if len(data) < 8 {
+ return false
+ }
+ m.ticketLifetime = uint32(data[4])<<24 | uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7])
+ data = data[8:]
+
+ if m.version >= VersionTLS13 {
+ if len(data) < 10 {
+ return false
+ }
+ m.ticketFlags = uint32(data[0])<<24 | uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3])
+ m.ticketAgeAdd = uint32(data[4])<<24 | uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7])
+ extsLength := int(data[8])<<8 + int(data[9])
+ data = data[10:]
+ if len(data) < extsLength {
+ return false
+ }
+ data = data[extsLength:]
+ }
+
+ if len(data) < 2 {
+ return false
+ }
+ ticketLen := int(data[0])<<8 + int(data[1])
+ if len(data)-2 != ticketLen {
+ return false
+ }
+ if m.version >= VersionTLS13 && ticketLen == 0 {
return false
}
- length := uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3])
- if uint32(len(data))-4 != length {
- return false
- }
-
- ticketLen := int(data[8])<<8 + int(data[9])
- if len(data)-10 != ticketLen {
- return false
- }
-
- m.ticket = data[10:]
+ m.ticket = data[2:]
return true
}
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index aeda2f1..a660f72 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -666,10 +666,19 @@
// Switch to application data keys on read.
c.in.updateKeys(deriveTrafficAEAD(c.vers, hs.suite, trafficSecret, applicationPhase, clientWrite), c.vers)
- // TODO(davidben): Derive and save the resumption master secret for receiving tickets.
// TODO(davidben): Save the traffic secret for KeyUpdate.
c.cipherSuite = hs.suite
c.exporterSecret = hs.finishedHash.deriveSecret(masterSecret, exporterLabel)
+ c.resumptionSecret = hs.finishedHash.deriveSecret(masterSecret, resumptionLabel)
+
+ // TODO(davidben): Allow configuring the number of tickets sent for
+ // testing.
+ if !c.config.SessionTicketsDisabled {
+ ticketCount := 2
+ for i := 0; i < ticketCount; i++ {
+ c.SendNewSessionTicket()
+ }
+ }
return nil
}