Ignore all extensions but renegotiation_info in SSL 3.0.

SSL 3.0 used to have a nice and simple rule around extensions. They don't
exist. And then RFC 5746 came along and made this all extremely confusing.

In an SSL 3.0 server, rather than blocking ServerHello extension
emission when renegotiation_info is missing, ignore all ClientHello
extensions but renegotiation_info. This avoids a mismatch between local
state and the extensions with emit.

Notably if, for some reason, a ClientHello includes the session_ticket
extension, does NOT include renegotiation_info or the SCSV, and yet the
client or server are decrepit enough to negotiate SSL 3.0, the
connection will fail due to unexpected NewSessionTicket message.

See https://crbug.com/425979#c9 for a discussion of something similar
that came up in diagnosing https://poodle.io/'s buggy POODLE check.
This is analogous to upstream's
5a3d8eebb7667b32af0ccc3f12f314df6809d32d.

(Not supporting renego as a server in any form anyway, we may as well
completely ignore extensions, but then our extensions callbacks can't
assume the parse hooks are always called. This way the various NULL
handlers still function.)

Change-Id: Ie689a0e9ffb0369ef7a20ab4231005e87f32d5f8
Reviewed-on: https://boringssl-review.googlesource.com/6180
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index a9823fc..61c8c57 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -149,6 +149,9 @@
 	// expectedNextProto controls whether the connection should
 	// negotiate a next protocol via NPN or ALPN.
 	expectedNextProto string
+	// expectNoNextProto, if true, means that no next protocol should be
+	// negotiated.
+	expectNoNextProto bool
 	// expectedNextProtoType, if non-zero, is the expected next
 	// protocol negotiation mechanism.
 	expectedNextProtoType int
@@ -328,6 +331,12 @@
 		}
 	}
 
+	if test.expectNoNextProto {
+		if actual := connState.NegotiatedProtocol; actual != "" {
+			return fmt.Errorf("got unexpected next proto %s", actual)
+		}
+	}
+
 	if test.expectedNextProtoType != 0 {
 		if (test.expectedNextProtoType == alpn) != connState.NegotiatedProtocolFromALPN {
 			return fmt.Errorf("next proto type mismatch")
@@ -3492,6 +3501,68 @@
 		// long.
 		flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com"},
 	})
+
+	// Extensions should not function in SSL 3.0.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "SSLv3Extensions-NoALPN",
+		config: Config{
+			MaxVersion: VersionSSL30,
+			NextProtos: []string{"foo", "bar", "baz"},
+		},
+		flags: []string{
+			"-select-alpn", "foo",
+		},
+		expectNoNextProto: true,
+	})
+
+	// Test session tickets separately as they follow a different codepath.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "SSLv3Extensions-NoTickets",
+		config: Config{
+			MaxVersion: VersionSSL30,
+			Bugs: ProtocolBugs{
+				// Historically, session tickets in SSL 3.0
+				// failed in different ways depending on whether
+				// the client supported renegotiation_info.
+				NoRenegotiationInfo: true,
+			},
+		},
+		resumeSession: true,
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "SSLv3Extensions-NoTickets2",
+		config: Config{
+			MaxVersion: VersionSSL30,
+		},
+		resumeSession: true,
+	})
+
+	// But SSL 3.0 does send and process renegotiation_info.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "SSLv3Extensions-RenegotiationInfo",
+		config: Config{
+			MaxVersion: VersionSSL30,
+			Bugs: ProtocolBugs{
+				RequireRenegotiationInfo: true,
+			},
+		},
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "SSLv3Extensions-RenegotiationInfo-SCSV",
+		config: Config{
+			MaxVersion: VersionSSL30,
+			Bugs: ProtocolBugs{
+				NoRenegotiationInfo:      true,
+				SendRenegotiationSCSV:    true,
+				RequireRenegotiationInfo: true,
+			},
+		},
+	})
 }
 
 func addResumptionVersionTests() {