Add SSL_set_renegotiate_mode.

Add a slightly richer API. Notably, one can configure ssl_renegotiate_once to
only accept the first renego.

Also, this API doesn't repeat the mistake I made with
SSL_set_reject_peer_renegotiations which is super-confusing with the negation.

Change-Id: I7eb5d534e3e6c553b641793f4677fe5a56451c71
Reviewed-on: https://boringssl-review.googlesource.com/6221
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 237ebf1..8d1f37b 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1114,9 +1114,11 @@
   if (config->install_ddos_callback) {
     SSL_CTX_set_dos_protection_cb(ssl_ctx, DDoSCallback);
   }
-  if (!config->reject_peer_renegotiations) {
-    /* Renegotiations are disabled by default. */
-    SSL_set_reject_peer_renegotiations(ssl.get(), 0);
+  if (config->renegotiate_once) {
+    SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_once);
+  }
+  if (config->renegotiate_freely) {
+    SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_freely);
   }
   if (!config->check_close_notify) {
     SSL_set_quiet_shutdown(ssl.get(), 1);
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index ed37b77..189854e 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -206,9 +206,9 @@
 	// 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
+	// renegotiate indicates the number of times the connection should be
+	// renegotiated during the exchange.
+	renegotiate int
 	// renegotiateCiphers is a list of ciphersuite ids that will be
 	// switched in just before renegotiation.
 	renegotiateCiphers []uint16
@@ -403,12 +403,14 @@
 		tlsConn.SendAlert(alertLevelWarning, alertUnexpectedMessage)
 	}
 
-	if test.renegotiate {
+	if test.renegotiate > 0 {
 		if test.renegotiateCiphers != nil {
 			config.CipherSuites = test.renegotiateCiphers
 		}
-		if err := tlsConn.Renegotiate(); err != nil {
-			return err
+		for i := 0; i < test.renegotiate; i++ {
+			if err := tlsConn.Renegotiate(); err != nil {
+				return err
+			}
 		}
 	} else if test.renegotiateCiphers != nil {
 		panic("renegotiateCiphers without renegotiate")
@@ -2710,8 +2712,11 @@
 	if protocol == tls {
 		tests = append(tests, testCase{
 			name:        "Renegotiate-Client",
-			renegotiate: true,
-			flags:       []string{"-expect-total-renegotiations", "1"},
+			renegotiate: 1,
+			flags: []string{
+				"-renegotiate-freely",
+				"-expect-total-renegotiations", "1",
+			},
 		})
 		// NPN on client and server; results in post-handshake message.
 		tests = append(tests, testCase{
@@ -3675,8 +3680,7 @@
 	testCases = append(testCases, testCase{
 		testType:           serverTest,
 		name:               "Renegotiate-Server-Forbidden",
-		renegotiate:        true,
-		flags:              []string{"-reject-peer-renegotiations"},
+		renegotiate:        1,
 		shouldFail:         true,
 		expectedError:      ":NO_RENEGOTIATION:",
 		expectedLocalError: "remote error: no renegotiation",
@@ -3715,28 +3719,33 @@
 				FailIfResumeOnRenego: true,
 			},
 		},
-		renegotiate: true,
-		flags:       []string{"-expect-total-renegotiations", "1"},
+		renegotiate: 1,
+		flags: []string{
+			"-renegotiate-freely",
+			"-expect-total-renegotiations", "1",
+		},
 	})
 	testCases = append(testCases, testCase{
 		name:        "Renegotiate-Client-EmptyExt",
-		renegotiate: true,
+		renegotiate: 1,
 		config: Config{
 			Bugs: ProtocolBugs{
 				EmptyRenegotiationInfo: true,
 			},
 		},
+		flags:         []string{"-renegotiate-freely"},
 		shouldFail:    true,
 		expectedError: ":RENEGOTIATION_MISMATCH:",
 	})
 	testCases = append(testCases, testCase{
 		name:        "Renegotiate-Client-BadExt",
-		renegotiate: true,
+		renegotiate: 1,
 		config: Config{
 			Bugs: ProtocolBugs{
 				BadRenegotiationInfo: true,
 			},
 		},
+		flags:         []string{"-renegotiate-freely"},
 		shouldFail:    true,
 		expectedError: ":RENEGOTIATION_MISMATCH:",
 	})
@@ -3753,54 +3762,58 @@
 	})
 	testCases = append(testCases, testCase{
 		name:        "Renegotiate-Client-NoExt-Allowed",
-		renegotiate: true,
+		renegotiate: 1,
 		config: Config{
 			Bugs: ProtocolBugs{
 				NoRenegotiationInfo: true,
 			},
 		},
-		flags: []string{"-expect-total-renegotiations", "1"},
+		flags: []string{
+			"-renegotiate-freely",
+			"-expect-total-renegotiations", "1",
+		},
 	})
 	testCases = append(testCases, testCase{
 		name:        "Renegotiate-Client-SwitchCiphers",
-		renegotiate: true,
+		renegotiate: 1,
 		config: Config{
 			CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
 		},
 		renegotiateCiphers: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-		flags:              []string{"-expect-total-renegotiations", "1"},
+		flags: []string{
+			"-renegotiate-freely",
+			"-expect-total-renegotiations", "1",
+		},
 	})
 	testCases = append(testCases, testCase{
 		name:        "Renegotiate-Client-SwitchCiphers2",
-		renegotiate: true,
+		renegotiate: 1,
 		config: Config{
 			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
 		},
 		renegotiateCiphers: []uint16{TLS_RSA_WITH_RC4_128_SHA},
-		flags:              []string{"-expect-total-renegotiations", "1"},
-	})
-	testCases = append(testCases, testCase{
-		name:               "Renegotiate-Client-Forbidden",
-		renegotiate:        true,
-		flags:              []string{"-reject-peer-renegotiations"},
-		shouldFail:         true,
-		expectedError:      ":NO_RENEGOTIATION:",
-		expectedLocalError: "remote error: no renegotiation",
+		flags: []string{
+			"-renegotiate-freely",
+			"-expect-total-renegotiations", "1",
+		},
 	})
 	testCases = append(testCases, testCase{
 		name:        "Renegotiate-SameClientVersion",
-		renegotiate: true,
+		renegotiate: 1,
 		config: Config{
 			MaxVersion: VersionTLS10,
 			Bugs: ProtocolBugs{
 				RequireSameRenegoClientVersion: true,
 			},
 		},
-		flags: []string{"-expect-total-renegotiations", "1"},
+		flags: []string{
+			"-renegotiate-freely",
+			"-expect-total-renegotiations", "1",
+		},
 	})
 	testCases = append(testCases, testCase{
 		name:        "Renegotiate-FalseStart",
-		renegotiate: true,
+		renegotiate: 1,
 		config: Config{
 			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
 			NextProtos:   []string{"foo"},
@@ -3808,10 +3821,52 @@
 		flags: []string{
 			"-false-start",
 			"-select-next-proto", "foo",
+			"-renegotiate-freely",
 			"-expect-total-renegotiations", "1",
 		},
 		shimWritesFirst: true,
 	})
+
+	// Client-side renegotiation controls.
+	testCases = append(testCases, testCase{
+		name:               "Renegotiate-Client-Forbidden-1",
+		renegotiate:        1,
+		shouldFail:         true,
+		expectedError:      ":NO_RENEGOTIATION:",
+		expectedLocalError: "remote error: no renegotiation",
+	})
+	testCases = append(testCases, testCase{
+		name:        "Renegotiate-Client-Once-1",
+		renegotiate: 1,
+		flags: []string{
+			"-renegotiate-once",
+			"-expect-total-renegotiations", "1",
+		},
+	})
+	testCases = append(testCases, testCase{
+		name:        "Renegotiate-Client-Freely-1",
+		renegotiate: 1,
+		flags: []string{
+			"-renegotiate-freely",
+			"-expect-total-renegotiations", "1",
+		},
+	})
+	testCases = append(testCases, testCase{
+		name:               "Renegotiate-Client-Once-2",
+		renegotiate:        2,
+		flags:              []string{"-renegotiate-once"},
+		shouldFail:         true,
+		expectedError:      ":NO_RENEGOTIATION:",
+		expectedLocalError: "remote error: no renegotiation",
+	})
+	testCases = append(testCases, testCase{
+		name:        "Renegotiate-Client-Freely-2",
+		renegotiate: 2,
+		flags: []string{
+			"-renegotiate-freely",
+			"-expect-total-renegotiations", "2",
+		},
+	})
 }
 
 func addDTLSReplayTests() {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 6503494..4e19be6 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -76,7 +76,6 @@
   { "-fail-second-ddos-callback", &TestConfig::fail_second_ddos_callback },
   { "-handshake-never-done", &TestConfig::handshake_never_done },
   { "-use-export-context", &TestConfig::use_export_context },
-  { "-reject-peer-renegotiations", &TestConfig::reject_peer_renegotiations },
   { "-no-legacy-server-connect", &TestConfig::no_legacy_server_connect },
   { "-tls-unique", &TestConfig::tls_unique },
   { "-use-async-private-key", &TestConfig::use_async_private_key },
@@ -95,7 +94,9 @@
   { "-microsoft-big-sslv3-buffer", &TestConfig::microsoft_big_sslv3_buffer },
   { "-verify-fail", &TestConfig::verify_fail },
   { "-verify-peer", &TestConfig::verify_peer },
-  { "-expect-verify-result", &TestConfig::expect_verify_result }
+  { "-expect-verify-result", &TestConfig::expect_verify_result },
+  { "-renegotiate-once", &TestConfig::renegotiate_once },
+  { "-renegotiate-freely", &TestConfig::renegotiate_freely },
 };
 
 const Flag<std::string> kStringFlags[] = {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index ea05271..4601851 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -77,7 +77,6 @@
   std::string export_label;
   std::string export_context;
   bool use_export_context = false;
-  bool reject_peer_renegotiations = false;
   bool no_legacy_server_connect = false;
   bool tls_unique = false;
   bool use_async_private_key = false;
@@ -98,6 +97,8 @@
   bool expect_verify_result = false;
   std::string signed_cert_timestamps;
   int expect_total_renegotiations = 0;
+  bool renegotiate_once = false;
+  bool renegotiate_freely = false;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_config);