Add ssl_renegotiate_ignore.

This option causes clients to ignore HelloRequest messages completely.
This can be suitable in cases where a server tries to perform concurrent
application data and handshake flow, e.g. because they are trying to
“renew” symmetric keys.

Change-Id: I2779f7eff30d82163f2c34a625ec91dc34fab548
Reviewed-on: https://boringssl-review.googlesource.com/6431
Reviewed-by: David Benjamin <davidben@chromium.org>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index f9e0d85..79d7205 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2663,6 +2663,7 @@
   ssl_renegotiate_never = 0,
   ssl_renegotiate_once,
   ssl_renegotiate_freely,
+  ssl_renegotiate_ignore,
 };
 
 /* SSL_set_renegotiate_mode configures how |ssl|, a client, reacts to
@@ -2671,8 +2672,10 @@
  *
  * The renegotiation mode defaults to |ssl_renegotiate_never|, but may be set
  * at any point in a connection's lifetime. Set it to |ssl_renegotiate_once| to
- * allow one renegotiation and |ssl_renegotiate_freely| to allow all
- * renegotiations.
+ * allow one renegotiation, |ssl_renegotiate_freely| to allow all
+ * renegotiations or |ssl_renegotiate_ignore| to ignore HelloRequest messages.
+ * Note that ignoring HelloRequest messages may cause the connection to stall
+ * if the server waits for the renegotiation to complete.
  *
  * There is no support in BoringSSL for initiating renegotiations as a client
  * or server. */
diff --git a/ssl/s3_pkt.c b/ssl/s3_pkt.c
index c50b315..7416d0e 100644
--- a/ssl/s3_pkt.c
+++ b/ssl/s3_pkt.c
@@ -346,6 +346,8 @@
       return ssl->s3->total_renegotiations == 0;
     case ssl_renegotiate_freely:
       return 1;
+    case ssl_renegotiate_ignore:
+      return 1;
   }
 
   assert(0);
@@ -567,6 +569,10 @@
       goto err;
     }
 
+    if (s->renegotiate_mode == ssl_renegotiate_ignore) {
+      goto start;
+    }
+
     /* Renegotiation is only supported at quiescent points in the application
      * protocol, namely in HTTPS, just before reading the HTTP response. Require
      * the record-layer be idle and avoid complexities of sending a handshake
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 32c572e..fe3dd6f 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1205,6 +1205,9 @@
   if (config->renegotiate_freely) {
     SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_freely);
   }
+  if (config->renegotiate_ignore) {
+    SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_ignore);
+  }
   if (!config->check_close_notify) {
     SSL_set_quiet_shutdown(ssl.get(), 1);
   }
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 7defec1..078c227 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -782,6 +782,11 @@
 	// connections where the client offers a non-empty session ID or session
 	// ticket.
 	FailIfSessionOffered bool
+
+	// SendHelloRequestBeforeEveryAppDataRecord, if true, causes a
+	// HelloRequest handshake message to be sent before each application
+	// data record. This only makes sense for a server.
+	SendHelloRequestBeforeEveryAppDataRecord bool
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index c911ad0..ab9e233 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -1152,6 +1152,10 @@
 		c.sendAlertLocked(alertLevelError, c.config.Bugs.SendSpuriousAlert)
 	}
 
+	if c.config.Bugs.SendHelloRequestBeforeEveryAppDataRecord {
+		c.writeRecord(recordTypeHandshake, []byte{typeHelloRequest, 0, 0, 0})
+	}
+
 	// SSL 3.0 and TLS 1.0 are susceptible to a chosen-plaintext
 	// attack when using block mode ciphers due to predictable IVs.
 	// This can be prevented by splitting each Application Data
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 4f0f8a3..ad8e12a 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -3920,6 +3920,28 @@
 			"-expect-total-renegotiations", "2",
 		},
 	})
+	testCases = append(testCases, testCase{
+		name: "Renegotiate-Client-NoIgnore",
+		config: Config{
+			Bugs: ProtocolBugs{
+				SendHelloRequestBeforeEveryAppDataRecord: true,
+			},
+		},
+		shouldFail:    true,
+		expectedError: ":NO_RENEGOTIATION:",
+	})
+	testCases = append(testCases, testCase{
+		name: "Renegotiate-Client-Ignore",
+		config: Config{
+			Bugs: ProtocolBugs{
+				SendHelloRequestBeforeEveryAppDataRecord: true,
+			},
+		},
+		flags: []string{
+			"-renegotiate-ignore",
+			"-expect-total-renegotiations", "0",
+		},
+	})
 }
 
 func addDTLSReplayTests() {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index abec0e1..8b540c3 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -96,6 +96,7 @@
   { "-expect-verify-result", &TestConfig::expect_verify_result },
   { "-renegotiate-once", &TestConfig::renegotiate_once },
   { "-renegotiate-freely", &TestConfig::renegotiate_freely },
+  { "-renegotiate-ignore", &TestConfig::renegotiate_ignore },
   { "-disable-npn", &TestConfig::disable_npn },
 };
 
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 23fa1f11..a72d66b 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -98,6 +98,7 @@
   int expect_total_renegotiations = 0;
   bool renegotiate_once = false;
   bool renegotiate_freely = false;
+  bool renegotiate_ignore = false;
   bool disable_npn = false;
 };