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>
This commit is contained in:
David Benjamin 2016-07-18 01:25:41 +02:00 committed by CQ bot account: commit-bot@chromium.org
parent 4528e2b477
commit 58104889ad
4 changed files with 110 additions and 30 deletions

View File

@ -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)

View File

@ -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
}

View File

@ -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)
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
}

View File

@ -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
}