Add SSL_send_fatal_alert.

WebRTC want to be able to send a random alert. Add an API for this.

Change-Id: Id3113d68f25748729fd9e9a91dbbfa93eead12c3
Reviewed-on: https://boringssl-review.googlesource.com/8950
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Adam Langley <agl@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 8328421..2608d87 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -737,6 +737,20 @@
   return ssl->s3->recv_shutdown == ssl_shutdown_close_notify;
 }
 
+int SSL_send_fatal_alert(SSL *ssl, uint8_t alert) {
+  if (ssl->s3->alert_dispatch) {
+    if (ssl->s3->send_alert[0] != SSL3_AL_FATAL ||
+        ssl->s3->send_alert[1] != alert) {
+      /* We are already attempting to write a different alert. */
+      OPENSSL_PUT_ERROR(SSL, SSL_R_PROTOCOL_IS_SHUTDOWN);
+      return -1;
+    }
+    return ssl->method->dispatch_alert(ssl);
+  }
+
+  return ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
+}
+
 int SSL_get_error(const SSL *ssl, int ret_code) {
   int reason;
   uint32_t err;
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index edf0fe0..dd7d3a5 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1017,6 +1017,17 @@
   return ret;
 }
 
+// DoSendFatalAlert calls |SSL_send_fatal_alert|, resolving any asynchronous
+// operations. It returns the result of the final |SSL_send_fatal_alert| call.
+static int DoSendFatalAlert(SSL *ssl, uint8_t alert) {
+  const TestConfig *config = GetTestConfig(ssl);
+  int ret;
+  do {
+    ret = SSL_send_fatal_alert(ssl, alert);
+  } while (config->async && RetryAsync(ssl, ret));
+  return ret;
+}
+
 // CheckHandshakeProperties checks, immediately after |ssl| completes its
 // initial handshake (or False Starts), whether all the properties are
 // consistent with the test configuration and invariants.
@@ -1472,6 +1483,13 @@
     }
   }
 
+  if (config->send_alert) {
+    if (DoSendFatalAlert(ssl.get(), SSL_AD_DECOMPRESSION_FAILURE) < 0) {
+      return false;
+    }
+    return true;
+  }
+
   if (config->write_different_record_sizes) {
     if (config->is_dtls) {
       fprintf(stderr, "write_different_record_sizes not supported for DTLS\n");
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index f8cb4d9..8ad9962 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -3335,6 +3335,14 @@
 		})
 	}
 
+	tests = append(tests, testCase{
+		name:               "ShimSendAlert",
+		flags:              []string{"-send-alert"},
+		shimWritesFirst:    true,
+		shouldFail:         true,
+		expectedLocalError: "remote error: decompression failure",
+	})
+
 	if config.protocol == tls {
 		tests = append(tests, testCase{
 			name: "Renegotiate-Client",
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index d242f7a..2fa1f17 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -104,6 +104,7 @@
   { "-use-old-client-cert-callback",
     &TestConfig::use_old_client_cert_callback },
   { "-use-null-client-ca-list", &TestConfig::use_null_client_ca_list },
+  { "-send-alert", &TestConfig::send_alert },
 };
 
 const Flag<std::string> kStringFlags[] = {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 690fbe7..f6a1f12 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -112,6 +112,7 @@
   bool use_old_client_cert_callback = false;
   int initial_timeout_duration_ms = 0;
   bool use_null_client_ca_list = false;
+  bool send_alert = false;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_config);