Add Data-less Zero-RTT support.
This adds support on the server and client to accept data-less early
data. The server will still fail to parse early data with any
contents, so this should remain disabled.
BUG=76
Change-Id: Id85d192d8e0360b8de4b6971511b5e8a0e8012f7
Reviewed-on: https://boringssl-review.googlesource.com/12921
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 167e872..65a8797 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -249,6 +249,7 @@
extendedMasterSecret bool // Whether an extended master secret was used to generate the session
sctList []byte
ocspResponse []byte
+ earlyALPN string
ticketCreationTime time.Time
ticketExpiration time.Time
ticketAgeAdd uint32
@@ -1146,10 +1147,26 @@
// SendEarlyData causes a TLS 1.3 client to send the provided data
// in application data records immediately after the ClientHello,
- // provided that the client has a PSK that is appropriate for sending
- // early data and includes that PSK in its ClientHello.
+ // provided that the client offers a TLS 1.3 session. It will do this
+ // whether or not the server advertised early data for the ticket.
SendEarlyData [][]byte
+ // ExpectEarlyDataAccepted causes a TLS 1.3 client to check that early data
+ // was accepted by the server.
+ ExpectEarlyDataAccepted bool
+
+ // AlwaysAcceptEarlyData causes a TLS 1.3 server to always accept early data
+ // regardless of ALPN mismatch.
+ AlwaysAcceptEarlyData bool
+
+ // AlwaysRejectEarlyData causes a TLS 1.3 server to always reject early data.
+ AlwaysRejectEarlyData bool
+
+ // SendEarlyDataExtension, if true, causes a TLS 1.3 server to send the
+ // early_data extension in EncryptedExtensions, independent of whether
+ // it was accepted.
+ SendEarlyDataExtension bool
+
// ExpectEarlyData causes a TLS 1.3 server to read application
// data after the ClientHello (assuming the server is able to
// derive the key under which the data is encrypted) before it
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 1bdca84..ffe4f34 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -1477,6 +1477,7 @@
ticketExpiration: c.config.time().Add(time.Duration(newSessionTicket.ticketLifetime) * time.Second),
ticketAgeAdd: newSessionTicket.ticketAgeAdd,
maxEarlyDataSize: newSessionTicket.maxEarlyDataSize,
+ earlyALPN: c.clientProtocol,
}
cacheKey := clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
@@ -1789,6 +1790,7 @@
ticketCreationTime: c.config.time(),
ticketExpiration: c.config.time().Add(time.Duration(m.ticketLifetime) * time.Second),
ticketAgeAdd: uint32(addBuffer[3])<<24 | uint32(addBuffer[2])<<16 | uint32(addBuffer[1])<<8 | uint32(addBuffer[0]),
+ earlyALPN: []byte(c.clientProtocol),
}
if !c.config.Bugs.SendEmptySessionTicket {
@@ -1798,7 +1800,6 @@
return err
}
}
-
c.out.Lock()
defer c.out.Unlock()
_, err = c.writeRecord(recordTypeHandshake, m.marshal())
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index bf38c1a..a3fc14a 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -330,7 +330,7 @@
}
var sendEarlyData bool
- if len(hello.pskIdentities) > 0 && session.maxEarlyDataSize > 0 && c.config.Bugs.SendEarlyData != nil {
+ if len(hello.pskIdentities) > 0 && c.config.Bugs.SendEarlyData != nil {
hello.hasEarlyData = true
sendEarlyData = true
}
@@ -1336,6 +1336,18 @@
c.srtpProtectionProfile = serverExtensions.srtpProtectionProfile
}
+ if c.vers >= VersionTLS13 && c.didResume {
+ if c.config.Bugs.ExpectEarlyDataAccepted && !serverExtensions.hasEarlyData {
+ c.sendAlert(alertHandshakeFailure)
+ return errors.New("tls: server did not accept early data when expected")
+ }
+
+ if !c.config.Bugs.ExpectEarlyDataAccepted && serverExtensions.hasEarlyData {
+ c.sendAlert(alertHandshakeFailure)
+ return errors.New("tls: server accepted early data when not expected")
+ }
+ }
+
return nil
}
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 64edd01..b1afc03 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -451,7 +451,7 @@
var pskIndex int
foundKEMode := bytes.IndexByte(pskKEModes, pskDHEKEMode) >= 0
- if foundKEMode {
+ if foundKEMode && !config.SessionTicketsDisabled {
for i, pskIdentity := range pskIdentities {
// TODO(svaldez): Check the obfuscatedTicketAge before accepting 0-RTT.
sessionState, ok := c.decryptTicket(pskIdentity.ticket)
@@ -579,6 +579,10 @@
c.writeRecord(recordTypeHandshake, helloRetryRequest.marshal())
c.flushHandshake()
+ if hs.clientHello.hasEarlyData {
+ c.skipEarlyData = true
+ }
+
// Read new ClientHello.
newMsg, err := c.readHandshake()
if err != nil {
@@ -591,6 +595,10 @@
}
hs.writeClientHash(newClientHello.marshal())
+ if newClientHello.hasEarlyData {
+ return errors.New("tls: EarlyData sent in new ClientHello")
+ }
+
applyBugsToClientHello(newClientHello, config)
// Check that the new ClientHello matches the old ClientHello,
@@ -628,6 +636,7 @@
newClientHelloCopy.pskIdentities[i].obfuscatedTicketAge = identity.obfuscatedTicketAge
}
newClientHelloCopy.pskBinders = oldClientHelloCopy.pskBinders
+ newClientHelloCopy.hasEarlyData = oldClientHelloCopy.hasEarlyData
if !oldClientHelloCopy.equal(&newClientHelloCopy) {
return errors.New("tls: new ClientHello does not match")
@@ -650,10 +659,13 @@
}
// Decide whether or not to accept early data.
- // TODO(nharper): This does not check that ALPN or SNI matches.
- if hs.clientHello.hasEarlyData {
- if !sendHelloRetryRequest && hs.sessionState != nil {
- encryptedExtensions.extensions.hasEarlyData = true
+ if !sendHelloRetryRequest && hs.clientHello.hasEarlyData {
+ if !config.Bugs.AlwaysRejectEarlyData && hs.sessionState != nil {
+ if c.clientProtocol == string(hs.sessionState.earlyALPN) || config.Bugs.AlwaysAcceptEarlyData {
+ encryptedExtensions.extensions.hasEarlyData = true
+ }
+ }
+ if encryptedExtensions.extensions.hasEarlyData {
earlyTrafficSecret := hs.finishedHash.deriveSecret(earlyTrafficLabel)
c.in.useTrafficSecret(c.vers, hs.suite, earlyTrafficSecret, clientWrite)
@@ -673,6 +685,10 @@
}
}
+ if config.Bugs.SendEarlyDataExtension {
+ encryptedExtensions.extensions.hasEarlyData = true
+ }
+
// Resolve ECDHE and compute the handshake secret.
if hs.hello.hasKeyShare {
// Once a curve has been selected and a key share identified,
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 563d4b2..262ac28 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -3562,6 +3562,40 @@
// Cover HelloRetryRequest during an ECDHE-PSK resumption.
resumeSession: true,
})
+
+ // TODO(svaldez): Send data on early data once implemented.
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "TLS13-EarlyData-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-accept-early-data",
+ },
+ })
+
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "TLS13-EarlyData-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{},
+ ExpectEarlyDataAccepted: true,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ },
+ })
}
// TLS client auth.
@@ -5957,8 +5991,8 @@
// In TLS 1.3, clients may advertise a cipher list which does not
// include the selected cipher. Test that we tolerate this. Servers may
- // resume at another cipher if the PRF matches, but BoringSSL will
- // always decline.
+ // resume at another cipher if the PRF matches and are not doing 0-RTT, but
+ // BoringSSL will always decline.
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-UnofferedCipher-TLS13",
@@ -9916,6 +9950,364 @@
},
},
})
+
+ // Test that we accept data-less early data.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-DataLessEarlyData-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{},
+ ExpectEarlyDataAccepted: true,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-accept-early-data",
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-accept-early-data",
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-Reject-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ AlwaysRejectEarlyData: true,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-reject-early-data",
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-HRR-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-reject-early-data",
+ },
+ })
+
+ // The client must check the server does not send the early_data
+ // extension while rejecting the session.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-EarlyDataWithoutResume-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ SendEarlyDataExtension: true,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ // The client must fail with a dedicated error code if the server
+ // responds with TLS 1.2 when offering 0-RTT.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-EarlyDataVersionDowngrade-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS12,
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_VERSION_ON_EARLY_DATA:",
+ })
+
+ // Test that the client rejects an (unsolicited) early_data extension if
+ // the server sent an HRR.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-ServerAcceptsEarlyDataOnHRR-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
+ SendEarlyDataExtension: true,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ fooString := "foo"
+ barString := "bar"
+
+ // Test that the client reports the correct ALPN after a 0-RTT reject
+ // that changed it.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-ALPNMismatch-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ ALPNProtocol: &fooString,
+ },
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ ALPNProtocol: &barString,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-reject-early-data",
+ "-expect-alpn", "foo",
+ "-expect-resume-alpn", "bar",
+ },
+ })
+
+ // Test that the client reports the correct ALPN after a 0-RTT reject if
+ // ALPN was omitted from the first connection.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-ALPNOmitted1-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ NextProtos: []string{"foo"},
+ },
+ resumeSession: true,
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-reject-early-data",
+ "-expect-no-alpn",
+ "-expect-resume-alpn", "foo",
+ },
+ })
+
+ // Test that the client reports the correct ALPN after a 0-RTT reject if
+ // ALPN was omitted from the second connection.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-ALPNOmitted2-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ NextProtos: []string{"foo"},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeSession: true,
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-reject-early-data",
+ "-expect-alpn", "foo",
+ "-expect-no-resume-alpn",
+ },
+ })
+
+ // Test that the client enforces ALPN match on 0-RTT accept.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-BadALPNMismatch-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ ALPNProtocol: &fooString,
+ },
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ AlwaysAcceptEarlyData: true,
+ ALPNProtocol: &barString,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ "-enable-early-data",
+ "-expect-early-data-info",
+ },
+ shouldFail: true,
+ expectedError: ":ALPN_MISMATCH_ON_EARLY_DATA:",
+ })
+
+ // Test that the server correctly rejects 0-RTT when the previous
+ // session did not allow early data on resumption.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-EarlyData-NonZeroRTTSession-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{{}},
+ ExpectEarlyDataAccepted: false,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-resume-early-data",
+ "-expect-reject-early-data",
+ },
+ })
+
+ // Test that we reject early data where ALPN is omitted from the first
+ // connection.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-EarlyData-ALPNOmitted1-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"foo"},
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{{}},
+ ExpectEarlyDataAccepted: false,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-select-alpn", "",
+ "-select-resume-alpn", "foo",
+ },
+ })
+
+ // Test that we reject early data where ALPN is omitted from the second
+ // connection.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-EarlyData-ALPNOmitted2-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"foo"},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{},
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{{}},
+ ExpectEarlyDataAccepted: false,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-select-alpn", "foo",
+ "-select-resume-alpn", "",
+ },
+ })
+
+ // Test that we reject early data with mismatched ALPN.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-EarlyData-ALPNMismatch-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"foo"},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"bar"},
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{{}},
+ ExpectEarlyDataAccepted: false,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-select-alpn", "foo",
+ "-select-resume-alpn", "bar",
+ },
+ })
+
}
func addTLS13CipherPreferenceTests() {
diff --git a/ssl/test/runner/ticket.go b/ssl/test/runner/ticket.go
index 4a4540c..10ac54f 100644
--- a/ssl/test/runner/ticket.go
+++ b/ssl/test/runner/ticket.go
@@ -25,6 +25,7 @@
handshakeHash []byte
certificates [][]byte
extendedMasterSecret bool
+ earlyALPN []byte
ticketCreationTime time.Time
ticketExpiration time.Time
ticketFlags uint32
@@ -58,6 +59,9 @@
msg.addU32(s.ticketAgeAdd)
}
+ earlyALPN := msg.addU16LengthPrefixed()
+ earlyALPN.addBytes(s.earlyALPN)
+
return msg.finish()
}
@@ -138,6 +142,14 @@
data = data[4:]
}
+ earlyALPNLen := int(data[0])<<8 | int(data[1])
+ data = data[2:]
+ if len(data) < earlyALPNLen {
+ return false
+ }
+ s.earlyALPN = data[:earlyALPNLen]
+ data = data[earlyALPNLen:]
+
if len(data) > 0 {
return false
}