diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go index 240a7ecd..fd957813 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go @@ -189,6 +189,13 @@ const ( 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 1b6c5573..8658fd2a 100644 --- a/ssl/test/runner/conn.go +++ b/ssl/test/runner/conn.go @@ -59,6 +59,7 @@ type Conn struct { clientRandom, serverRandom [32]byte exporterSecret []byte + resumptionSecret []byte clientProtocol string clientProtocolFallback bool @@ -1143,7 +1144,9 @@ func (c *Conn) readHandshake() (interface{}, error) { 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 @@ func (c *Conn) noRenegotiationInfo() bool { } 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 15bafa07..41a8fb22 100644 --- a/ssl/test/runner/handshake_messages.go +++ b/ssl/test/runner/handshake_messages.go @@ -1715,50 +1715,75 @@ func (m *certificateVerifyMsg) unmarshal(data []byte) bool { } 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) - - m.raw = x + 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) - 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:] - length := uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3]) - if uint32(len(data))-4 != length { - return false + 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:] } - ticketLen := int(data[8])<<8 + int(data[9]) - if len(data)-10 != ticketLen { + 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 } - 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 aeda2f15..a660f72f 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go @@ -666,10 +666,19 @@ Curves: // 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 }