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
 }