Allow renego and config shedding to coexist more smoothly.

Chrome needs to support renegotiation at TLS 1.2 + HTTP/1.1, but we're
free to shed the handshake configuration at TLS 1.3 or HTTP/2.

Rather than making config shedding implicitly disable renegotiation,
make the actual shedding dependent on a combination of the two settings.
If config shedding is enabled, but so is renegotiation (including
whether we are a client, etc.), leave the config around. If the
renegotiation setting gets disabled again after the handshake,
re-evaluate and shed the config then.

Bug: 123
Change-Id: Ie833f413b3f15b8f0ede617991e3fef239d4a323
Reviewed-on: https://boringssl-review.googlesource.com/27904
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
Reviewed-by: Matt Braithwaite <mab@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 2b87914..ee879c8 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -3304,10 +3304,17 @@
     SSL_CTX *ctx, void (*cb)(const SSL *ssl, struct timeval *out_clock));
 
 // SSL_set_shed_handshake_config allows some of the configuration of |ssl| to be
-// freed after its handshake completes.  When configuration shedding is enabled,
+// freed after its handshake completes. When configuration shedding is enabled,
 // it is an error to call APIs that query the state that was shed, and it is an
-// error to call |SSL_clear|.  Additionally, if renegotiation is enabled,
-// renegotiation attempts will be rejected.
+// error to call |SSL_clear|.
+//
+// Note that configuration shedding as a client additionally depends on
+// renegotiation being disabled (see |SSL_set_renegotiate_mode|). If
+// renegotiation is possible, the configuration will be retained. If
+// configuration shedding is enabled and renegotiation later disabled after the
+// handshake, |SSL_set_renegotiate_mode| will shed configuration then. This may
+// be useful for clients which support renegotiation with some ALPN protocols,
+// such as HTTP/1.1, and not others, such as HTTP/2.
 OPENSSL_EXPORT void SSL_set_shed_handshake_config(SSL *ssl, int enable);
 
 enum ssl_renegotiate_mode_t {
@@ -3328,6 +3335,13 @@
 // Note that ignoring HelloRequest messages may cause the connection to stall
 // if the server waits for the renegotiation to complete.
 //
+// If configuration shedding is enabled (see |SSL_set_shed_handshake_config|),
+// configuration is released if, at any point after the handshake, renegotiation
+// is disabled. It is not possible to switch from disabling renegotiation to
+// enabling it on a given connection. Callers that condition renegotiation on,
+// e.g., ALPN must enable renegotiation before the handshake and conditionally
+// disable it afterwards.
+//
 // There is no support in BoringSSL for initiating renegotiations as a client
 // or server.
 OPENSSL_EXPORT void SSL_set_renegotiate_mode(SSL *ssl,
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 1e5b3d1..7b80a03 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -459,6 +459,50 @@
   ctx->handoff = on;
 }
 
+static bool ssl_can_renegotiate(const SSL *ssl) {
+  if (ssl->server || SSL_is_dtls(ssl)) {
+    return false;
+  }
+
+  // We do not accept at SSL 3.0. SSL 3.0 will be removed entirely in the future
+  // and requires retaining more data for renegotiation_info.
+  uint16_t version = ssl_protocol_version(ssl);
+  if (version == SSL3_VERSION || version >= TLS1_3_VERSION) {
+    return false;
+  }
+
+  // The config has already been shed.
+  if (!ssl->config) {
+    return false;
+  }
+
+  switch (ssl->renegotiate_mode) {
+    case ssl_renegotiate_ignore:
+    case ssl_renegotiate_never:
+      return false;
+
+    case ssl_renegotiate_freely:
+      return true;
+    case ssl_renegotiate_once:
+      return ssl->s3->total_renegotiations == 0;
+  }
+
+  assert(0);
+  return false;
+}
+
+static void ssl_maybe_shed_handshake_config(SSL *ssl) {
+  if (ssl->s3->hs != nullptr ||
+      ssl->config == nullptr ||
+      !ssl->config->shed_handshake_config ||
+      ssl_can_renegotiate(ssl)) {
+    return;
+  }
+
+  Delete(ssl->config);
+  ssl->config = nullptr;
+}
+
 }  // namespace bssl
 
 using namespace bssl;
@@ -881,13 +925,8 @@
 
   // Destroy the handshake object if the handshake has completely finished.
   if (!early_return) {
-    if (hs->config->shed_handshake_config) {
-      assert(ssl->config == hs->config);
-      Delete(ssl->config);
-      ssl->config = nullptr;
-      hs->config = nullptr;
-    }
     ssl->s3->hs.reset();
+    ssl_maybe_shed_handshake_config(ssl);
   }
 
   return 1;
@@ -916,11 +955,12 @@
     return tls13_post_handshake(ssl, msg);
   }
 
-  // We do not accept renegotiations as a server or SSL 3.0. SSL 3.0 will be
-  // removed entirely in the future and requires retaining more data for
-  // renegotiation_info.
-  if (ssl->server || ssl->version == SSL3_VERSION) {
-    goto no_renegotiation;
+  // Check for renegotiation on the server before parsing to use the correct
+  // error. Renegotiation is triggered by a different message for servers.
+  if (ssl->server) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_NO_RENEGOTIATION);
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_NO_RENEGOTIATION);
+    return 0;
   }
 
   if (msg.type != SSL3_MT_HELLO_REQUEST || CBS_len(&msg.body) != 0) {
@@ -929,36 +969,20 @@
     return 0;
   }
 
-  switch (ssl->renegotiate_mode) {
-    case ssl_renegotiate_ignore:
-      // Ignore the HelloRequest.
-      return 1;
-
-    case ssl_renegotiate_once:
-      if (ssl->s3->total_renegotiations != 0) {
-        goto no_renegotiation;
-      }
-      break;
-
-    case ssl_renegotiate_never:
-      goto no_renegotiation;
-
-    case ssl_renegotiate_freely:
-      break;
+  if (ssl->renegotiate_mode == ssl_renegotiate_ignore) {
+    return 1;  // Ignore the HelloRequest.
   }
 
-  // Reject renegotiation when the handshake config has been shed.
-  if (!ssl->config) {
-    goto no_renegotiation;
-  }
-
-  // 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
-  // record while an application_data record is being written.
-  if (!ssl->s3->write_buffer.empty() ||
+  if (!ssl_can_renegotiate(ssl) ||
+      // 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 record while an application_data record is being written.
+      !ssl->s3->write_buffer.empty() ||
       ssl->s3->write_shutdown != ssl_shutdown_none) {
-    goto no_renegotiation;
+    OPENSSL_PUT_ERROR(SSL, SSL_R_NO_RENEGOTIATION);
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_NO_RENEGOTIATION);
+    return 0;
   }
 
   // Begin a new handshake.
@@ -973,11 +997,6 @@
 
   ssl->s3->total_renegotiations++;
   return 1;
-
-no_renegotiation:
-  OPENSSL_PUT_ERROR(SSL, SSL_R_NO_RENEGOTIATION);
-  ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_NO_RENEGOTIATION);
-  return 0;
 }
 
 static int ssl_read_impl(SSL *ssl) {
@@ -2643,6 +2662,11 @@
 
 void SSL_set_renegotiate_mode(SSL *ssl, enum ssl_renegotiate_mode_t mode) {
   ssl->renegotiate_mode = mode;
+
+  // Check if |ssl_can_renegotiate| has changed and the configuration may now be
+  // shed. HTTP clients may initially allow renegotiation for HTTP/1.1, and then
+  // disable after the handshake once the ALPN protocol is known to be HTTP/2.
+  ssl_maybe_shed_handshake_config(ssl);
 }
 
 int SSL_get_ivs(const SSL *ssl, const uint8_t **out_read_iv,
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index dae759c..1b37a8d 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -2069,11 +2069,11 @@
   SSL_set_shed_handshake_config(ssl.get(), true);
   if (config->renegotiate_once) {
     SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_once);
-    SSL_set_shed_handshake_config(ssl.get(), config->shed_despite_renegotiate);
   }
-  if (config->renegotiate_freely) {
+  if (config->renegotiate_freely ||
+      config->forbid_renegotiation_after_handshake) {
+    // |forbid_renegotiation_after_handshake| will disable renegotiation later.
     SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_freely);
-    SSL_set_shed_handshake_config(ssl.get(), config->shed_despite_renegotiate);
   }
   if (config->renegotiate_ignore) {
     SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_ignore);
@@ -2378,6 +2378,10 @@
       } while (config->async && RetryAsync(ssl, ret));
     }
 
+    if (config->forbid_renegotiation_after_handshake) {
+      SSL_set_renegotiate_mode(ssl, ssl_renegotiate_never);
+    }
+
     if (ret != 1 || !CheckHandshakeProperties(ssl, is_resume, config)) {
       return false;
     }
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index d85efca..38607d2 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -8209,14 +8209,15 @@
 		},
 	})
 
-	// Renegotiation should be rejected if the handshake config has been shed.
+	// Renegotiation may be enabled and then disabled immediately after the
+	// handshake.
 	testCases = append(testCases, testCase{
-		name: "Renegotiate-HandshakeConfigShed",
+		name: "Renegotiate-ForbidAfterHandshake",
 		config: Config{
 			MaxVersion: VersionTLS12,
 		},
 		renegotiate:        1,
-		flags:              []string{"-renegotiate-freely", "-shed-despite-renegotiate"},
+		flags:              []string{"-forbid-renegotiation-after-handshake"},
 		shouldFail:         true,
 		expectedError:      ":NO_RENEGOTIATION:",
 		expectedLocalError: "remote error: no renegotiation",
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 9433e4c..2ab5bfe 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -98,10 +98,11 @@
   { "-verify-peer", &TestConfig::verify_peer },
   { "-verify-peer-if-no-obc", &TestConfig::verify_peer_if_no_obc },
   { "-expect-verify-result", &TestConfig::expect_verify_result },
-  { "-shed-despite-renegotiate", &TestConfig::shed_despite_renegotiate },
   { "-renegotiate-once", &TestConfig::renegotiate_once },
   { "-renegotiate-freely", &TestConfig::renegotiate_freely },
   { "-renegotiate-ignore", &TestConfig::renegotiate_ignore },
+  { "-forbid-renegotiation-after-handshake",
+    &TestConfig::forbid_renegotiation_after_handshake },
   { "-p384-only", &TestConfig::p384_only },
   { "-enable-all-curves", &TestConfig::enable_all_curves },
   { "-use-old-client-cert-callback",
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 4a03fba..9a026ce 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -115,7 +115,7 @@
   bool renegotiate_once = false;
   bool renegotiate_freely = false;
   bool renegotiate_ignore = false;
-  bool shed_despite_renegotiate = false;
+  bool forbid_renegotiation_after_handshake = false;
   int expect_peer_signature_algorithm = 0;
   bool p384_only = false;
   bool enable_all_curves = false;