Skipping early data on 0RTT rejection.
BUG=101
Change-Id: Ia1edbccee535b0bc3a0e18465286d5bcca240035
Reviewed-on: https://boringssl-review.googlesource.com/12470
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/internal.h b/ssl/internal.h
index 1d78ec1..5893d4d 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1393,6 +1393,10 @@
* completed. */
unsigned initial_handshake_complete:1;
+ /* skip_early_data instructs the record layer to skip unexpected early data
+ * messages when 0RTT is rejected. */
+ unsigned skip_early_data:1;
+
/* read_buffer holds data from the transport to be processed. */
SSL3_BUFFER read_buffer;
/* write_buffer holds data to be written to the transport. */
@@ -1423,6 +1427,10 @@
/* recv_shutdown is the shutdown state for the send half of the connection. */
enum ssl_shutdown_t send_shutdown;
+ /* early_data_skipped is the amount of early data that has been skipped by the
+ * record layer. */
+ uint16_t early_data_skipped;
+
int alert_dispatch;
uint8_t send_alert[2];
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index 421232f..21005f0 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -2034,6 +2034,7 @@
/* Pre-Shared Key Exchange Modes
*
* https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.2.7 */
+
static int ext_psk_key_exchange_modes_add_clienthello(SSL *ssl, CBB *out) {
uint16_t min_version, max_version;
if (!ssl_get_version_range(ssl, &min_version, &max_version)) {
@@ -2078,6 +2079,35 @@
}
+/* Early Data Indication
+ *
+ * https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.2.8 */
+
+static int ext_early_data_add_clienthello(SSL *ssl, CBB *out) {
+ /* TODO(svaldez): Support 0RTT. */
+ return 1;
+}
+
+static int ext_early_data_parse_clienthello(SSL *ssl, uint8_t *out_alert,
+ CBS *contents) {
+ if (contents == NULL) {
+ return 1;
+ }
+
+ if (CBS_len(contents) != 0) {
+ *out_alert = SSL_AD_DECODE_ERROR;
+ return 0;
+ }
+
+ /* Since we don't currently accept 0-RTT, we have to skip past any early data
+ * the client might have sent. */
+ if (ssl3_protocol_version(ssl) >= TLS1_3_VERSION) {
+ ssl->s3->skip_early_data = 1;
+ }
+ return 1;
+}
+
+
/* Key Share
*
* https://tools.ietf.org/html/draft-ietf-tls-tls13-16#section-4.2.5 */
@@ -2561,6 +2591,14 @@
dont_add_serverhello,
},
{
+ TLSEXT_TYPE_early_data,
+ NULL,
+ ext_early_data_add_clienthello,
+ forbid_parse_serverhello,
+ ext_early_data_parse_clienthello,
+ dont_add_serverhello,
+ },
+ {
TLSEXT_TYPE_supported_versions,
NULL,
ext_supported_versions_add_clienthello,
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index a6496fd..caa9cde 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -1109,6 +1109,25 @@
// copies of each KeyShareEntry.
DuplicateKeyShares bool
+ // SendEarlyAlert, if true, sends a fatal alert after the ClientHello.
+ SendEarlyAlert bool
+
+ // SendEarlyDataLength, if non-zero, is the amount of early data to send after
+ // the ClientHello.
+ SendEarlyDataLength int
+
+ // OmitEarlyDataExtension, if true, causes the early data extension to
+ // be omitted in the ClientHello.
+ OmitEarlyDataExtension bool
+
+ // SendEarlyDataOnSecondClientHello, if true, causes the TLS 1.3 client to
+ // send early data after the second ClientHello.
+ SendEarlyDataOnSecondClientHello bool
+
+ // InterleaveEarlyData, if true, causes the TLS 1.3 client to send early
+ // data interleaved with the second ClientHello and the client Finished.
+ InterleaveEarlyData bool
+
// EmptyEncryptedExtensions, if true, causes the TLS 1.3 server to
// emit an empty EncryptedExtensions block.
EmptyEncryptedExtensions bool
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 39c2785..80a5b06 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -1768,3 +1768,16 @@
c.out.doKeyUpdate(c, true)
return nil
}
+
+func (c *Conn) sendFakeEarlyData(len int) error {
+ // Assemble a fake early data record. This does not use writeRecord
+ // because the record layer may be using different keys at this point.
+ payload := make([]byte, 5+len)
+ payload[0] = byte(recordTypeApplicationData)
+ payload[1] = 3
+ payload[2] = 1
+ payload[3] = byte(len >> 8)
+ payload[4] = byte(len)
+ _, err := c.conn.Write(payload)
+ return err
+}
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index d074778..2ebd0dc 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -319,6 +319,10 @@
hello.cipherSuites = c.config.Bugs.SendCipherSuites
}
+ if c.config.Bugs.SendEarlyDataLength > 0 && !c.config.Bugs.OmitEarlyDataExtension {
+ hello.hasEarlyData = true
+ }
+
var helloBytes []byte
if c.config.Bugs.SendV2ClientHello {
// Test that the peer left-pads random.
@@ -356,6 +360,12 @@
if err := c.simulatePacketLoss(nil); err != nil {
return err
}
+ if c.config.Bugs.SendEarlyAlert {
+ c.sendAlert(alertHandshakeFailure)
+ }
+ if c.config.Bugs.SendEarlyDataLength > 0 {
+ c.sendFakeEarlyData(c.config.Bugs.SendEarlyDataLength)
+ }
msg, err := c.readHandshake()
if err != nil {
return err
@@ -452,16 +462,28 @@
hello.hasKeyShares = false
}
- hello.hasEarlyData = false
+ hello.hasEarlyData = c.config.Bugs.SendEarlyDataOnSecondClientHello
hello.raw = nil
if len(hello.pskIdentities) > 0 {
generatePSKBinders(hello, pskCipherSuite, session.masterSecret, append(helloBytes, helloRetryRequest.marshal()...), c.config)
}
secondHelloBytes = hello.marshal()
- c.writeRecord(recordTypeHandshake, secondHelloBytes)
+
+ if c.config.Bugs.InterleaveEarlyData {
+ c.sendFakeEarlyData(4)
+ c.writeRecord(recordTypeHandshake, secondHelloBytes[:16])
+ c.sendFakeEarlyData(4)
+ c.writeRecord(recordTypeHandshake, secondHelloBytes[16:])
+ } else {
+ c.writeRecord(recordTypeHandshake, secondHelloBytes)
+ }
c.flushHandshake()
+ if c.config.Bugs.SendEarlyDataOnSecondClientHello {
+ c.sendFakeEarlyData(4)
+ }
+
msg, err = c.readHandshake()
if err != nil {
return err
@@ -871,6 +893,12 @@
if c.config.Bugs.PartialClientFinishedWithClientHello {
// The first byte has already been sent.
c.writeRecord(recordTypeHandshake, finished.marshal()[1:])
+ } else if c.config.Bugs.InterleaveEarlyData {
+ finishedBytes := finished.marshal()
+ c.sendFakeEarlyData(4)
+ c.writeRecord(recordTypeHandshake, finishedBytes[:1])
+ c.sendFakeEarlyData(4)
+ c.writeRecord(recordTypeHandshake, finishedBytes[1:])
} else {
c.writeRecord(recordTypeHandshake, finished.marshal())
}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 683f07c..35742cb 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -5568,7 +5568,7 @@
"-signed-cert-timestamps",
base64.StdEncoding.EncodeToString([]byte{0, 0}),
},
- shouldFail: true,
+ shouldFail: true,
expectedError: ":INVALID_SCT_LIST:",
})
}
@@ -9033,6 +9033,144 @@
})
testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyDataLength: 4,
+ },
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-OmitEarlyDataExtension",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyDataLength: 4,
+ OmitEarlyDataExtension: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-TooMuchData",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyDataLength: 16384 + 1,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":TOO_MUCH_SKIPPED_EARLY_DATA:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-Interleaved",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyDataLength: 4,
+ InterleaveEarlyData: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-EarlyDataInTLS12",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyDataLength: 4,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-HRR",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyDataLength: 4,
+ },
+ DefaultCurves: []CurveID{},
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-HRR-Interleaved",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyDataLength: 4,
+ InterleaveEarlyData: true,
+ },
+ DefaultCurves: []CurveID{},
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-HRR-TooMuchData",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyDataLength: 16384 + 1,
+ },
+ DefaultCurves: []CurveID{},
+ },
+ shouldFail: true,
+ expectedError: ":TOO_MUCH_SKIPPED_EARLY_DATA:",
+ })
+
+ // Test that skipping early data looking for cleartext correctly
+ // processes an alert record.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-HRR-FatalAlert",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyAlert: true,
+ SendEarlyDataLength: 4,
+ },
+ DefaultCurves: []CurveID{},
+ },
+ shouldFail: true,
+ expectedError: ":SSLV3_ALERT_HANDSHAKE_FAILURE:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SkipEarlyData-SecondClientHelloEarlyData",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyDataOnSecondClientHello: true,
+ },
+ DefaultCurves: []CurveID{},
+ },
+ shouldFail: true,
+ expectedLocalError: "remote error: bad record MAC",
+ })
+
+ testCases = append(testCases, testCase{
testType: clientTest,
name: "EmptyEncryptedExtensions",
config: Config{
diff --git a/ssl/tls_record.c b/ssl/tls_record.c
index 5931922..c52909c 100644
--- a/ssl/tls_record.c
+++ b/ssl/tls_record.c
@@ -125,6 +125,12 @@
* forever. */
static const uint8_t kMaxEmptyRecords = 32;
+/* kMaxEarlyDataSkipped is the maximum amount of data processed when skipping
+ * over early data. Without this limit an attacker could send records at a
+ * faster rate than we can process and cause trial decryption to loop
+ * forever. */
+static const size_t kMaxEarlyDataSkipped = 16384;
+
/* kMaxWarningAlerts is the number of consecutive warning alerts that will be
* processed. */
static const uint8_t kMaxWarningAlerts = 4;
@@ -246,15 +252,32 @@
ssl_do_msg_callback(ssl, 0 /* read */, SSL3_RT_HEADER, in,
SSL3_RT_HEADER_LENGTH);
+ *out_consumed = in_len - CBS_len(&cbs);
+
+ /* Skip early data received when expecting a second ClientHello if we rejected
+ * 0RTT. */
+ if (ssl->s3->skip_early_data &&
+ ssl->s3->aead_read_ctx == NULL &&
+ type == SSL3_RT_APPLICATION_DATA) {
+ goto skipped_data;
+ }
+
/* Decrypt the body in-place. */
if (!SSL_AEAD_CTX_open(ssl->s3->aead_read_ctx, out, type, version,
ssl->s3->read_sequence, (uint8_t *)CBS_data(&body),
CBS_len(&body))) {
+ if (ssl->s3->skip_early_data &&
+ ssl->s3->aead_read_ctx != NULL) {
+ ERR_clear_error();
+ goto skipped_data;
+ }
+
OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC);
*out_alert = SSL_AD_BAD_RECORD_MAC;
return ssl_open_record_error;
}
- *out_consumed = in_len - CBS_len(&cbs);
+
+ ssl->s3->skip_early_data = 0;
if (!ssl_record_sequence_update(ssl->s3->read_sequence, 8)) {
*out_alert = SSL_AD_INTERNAL_ERROR;
@@ -310,6 +333,20 @@
*out_type = type;
return ssl_open_record_success;
+
+skipped_data:
+ ssl->s3->early_data_skipped += *out_consumed;
+ if (ssl->s3->early_data_skipped < *out_consumed) {
+ ssl->s3->early_data_skipped = kMaxEarlyDataSkipped + 1;
+ }
+
+ if (ssl->s3->early_data_skipped > kMaxEarlyDataSkipped) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_TOO_MUCH_SKIPPED_EARLY_DATA);
+ *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+ return ssl_open_record_error;
+ }
+
+ return ssl_open_record_discard;
}
static int do_seal_record(SSL *ssl, uint8_t *out, size_t *out_len,