Add tests for bidirectional shutdown.
Now that it even works at all (type = 0 bug aside), add tests for it.
Test both close_notify being received before and after SSL_shutdown is
called. In the latter case, have the peer send some junk to be ignored
to test that works.
Also test that SSL_shutdown fails on unclean shutdown and that quiet
shutdowns ignore it.
BUG=526437
Change-Id: Iff13b08feb03e82f21ecab0c66d5f85aec256137
Reviewed-on: https://boringssl-review.googlesource.com/5769
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index f841293..2b7e29b 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -753,6 +753,15 @@
// ExpectedCustomExtension, if not nil, contains the expected contents
// of a custom extension.
ExpectedCustomExtension *string
+
+ // NoCloseNotify, if true, causes the close_notify alert to be skipped
+ // on connection shutdown.
+ NoCloseNotify bool
+
+ // ExpectCloseNotify, if true, requires a close_notify from the peer on
+ // shutdown. Records from the peer received after close_notify is sent
+ // are not discard.
+ ExpectCloseNotify bool
}
func (c *Config) serverInit() {
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index f09cb7c..42bc840 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -648,10 +648,10 @@
if err := b.readFromUntil(c.conn, recordHeaderLen); err != nil {
// RFC suggests that EOF without an alertCloseNotify is
// an error, but popular web sites seem to do this,
- // so we can't make it an error.
- // if err == io.EOF {
- // err = io.ErrUnexpectedEOF
- // }
+ // so we can't make it an error, outside of tests.
+ if err == io.EOF && c.config.Bugs.ExpectCloseNotify {
+ err = io.ErrUnexpectedEOF
+ }
if e, ok := err.(net.Error); !ok || !e.Temporary() {
c.in.setErrorLocked(err)
}
@@ -740,6 +740,10 @@
c.sendAlert(alertInternalError)
return c.in.setErrorLocked(errors.New("tls: application data record requested before handshake complete"))
}
+ case recordTypeAlert:
+ // Looking for a close_notify. Note: unlike a real
+ // implementation, this is not tolerant of additional records.
+ // See the documentation for ExpectCloseNotify.
}
Again:
@@ -802,7 +806,7 @@
// A client might need to process a HelloRequest from
// the server, thus receiving a handshake message when
// application data is expected is ok.
- if !c.isClient {
+ if !c.isClient || want != recordTypeApplicationData {
return c.in.setErrorLocked(c.sendAlert(alertNoRenegotiation))
}
}
@@ -1260,10 +1264,22 @@
c.handshakeMutex.Lock()
defer c.handshakeMutex.Unlock()
- if c.handshakeComplete {
+ if c.handshakeComplete && !c.config.Bugs.NoCloseNotify {
alertErr = c.sendAlert(alertCloseNotify)
}
+ // Consume a close_notify from the peer if one hasn't been received
+ // already. This avoids the peer from failing |SSL_shutdown| due to a
+ // write failing.
+ if c.handshakeComplete && alertErr == nil && c.config.Bugs.ExpectCloseNotify {
+ for c.in.error() == nil {
+ c.readRecord(recordTypeAlert)
+ }
+ if c.in.error() != io.EOF {
+ alertErr = c.in.error()
+ }
+ }
+
if err := c.conn.Close(); err != nil {
return err
}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 950c02a..7ada5f1 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -191,6 +191,10 @@
// shimWritesFirst controls whether the shim sends an initial "hello"
// message before doing a roundtrip with the runner.
shimWritesFirst bool
+ // shimShutsDown, if true, runs a test where the shim shuts down the
+ // connection immediately after the handshake rather than echoing
+ // messages from the runner.
+ shimShutsDown bool
// renegotiate indicates the the connection should be renegotiated
// during the exchange.
renegotiate bool
@@ -270,6 +274,7 @@
tlsConn = Client(conn, config)
}
}
+ defer tlsConn.Close()
if err := tlsConn.Handshake(); err != nil {
return err
@@ -420,6 +425,11 @@
tlsConn.SendAlert(alertLevelWarning, alertUnexpectedMessage)
}
+ if test.shimShutsDown {
+ // The shim will not respond.
+ continue
+ }
+
buf := make([]byte, len(testMessage))
if test.protocol == dtls {
bufTmp := make([]byte, len(buf)+1)
@@ -547,6 +557,10 @@
flags = append(flags, "-shim-writes-first")
}
+ if test.shimShutsDown {
+ flags = append(flags, "-shim-shuts-down")
+ }
+
if test.exportKeyingMaterial > 0 {
flags = append(flags, "-export-keying-material", strconv.Itoa(test.exportKeyingMaterial))
flags = append(flags, "-export-label", test.exportLabel)
@@ -1847,8 +1861,29 @@
noSessionCache: true,
flags: []string{"-expect-no-session"},
},
+ {
+ name: "Unclean-Shutdown",
+ config: Config{
+ Bugs: ProtocolBugs{
+ NoCloseNotify: true,
+ ExpectCloseNotify: true,
+ },
+ },
+ shimShutsDown: true,
+ flags: []string{"-check-close-notify"},
+ shouldFail: true,
+ expectedError: "Unexpected SSL_shutdown result: -1 != 1",
+ },
+ {
+ name: "Unclean-Shutdown-Ignored",
+ config: Config{
+ Bugs: ProtocolBugs{
+ NoCloseNotify: true,
+ },
+ },
+ shimShutsDown: true,
+ },
}
-
testCases = append(testCases, basicTests...)
}
@@ -2561,6 +2596,33 @@
resumeSession: true,
expectChannelID: true,
})
+
+ // Bidirectional shutdown with the runner initiating.
+ tests = append(tests, testCase{
+ name: "Shutdown-Runner",
+ config: Config{
+ Bugs: ProtocolBugs{
+ ExpectCloseNotify: true,
+ },
+ },
+ flags: []string{"-check-close-notify"},
+ })
+
+ // Bidirectional shutdown with the shim initiating. The runner,
+ // in the meantime, sends garbage before the close_notify which
+ // the shim must ignore.
+ tests = append(tests, testCase{
+ name: "Shutdown-Shim",
+ config: Config{
+ Bugs: ProtocolBugs{
+ ExpectCloseNotify: true,
+ },
+ },
+ shimShutsDown: true,
+ sendEmptyRecords: 1,
+ sendWarningAlerts: 1,
+ flags: []string{"-check-close-notify"},
+ })
} else {
tests = append(tests, testCase{
name: "SkipHelloVerifyRequest",