Implement legacy OCSP APIs for libssl.

Previously, we'd omitted OpenSSL's OCSP APIs because they depend on a
complex OCSP mechanism and encourage the the unreliable server behavior
that hampers using OCSP stapling to fix revocation today. (OCSP
responses should not be fetched on-demand on a callback. They should be
managed like other server credentials and refreshed eagerly, so
temporary CA outage does not translate to loss of OCSP.)

But most of the APIs are byte-oriented anyway, so they're easy to
support. Intentionally omit the one that takes a bunch of OCSP_RESPIDs.

The callback is benign on the client (an artifact of OpenSSL reading
OCSP and verifying certificates in the wrong order). On the server, it
encourages unreliability, but pyOpenSSL/cryptography.io depends on this.
Dcument that this is only for compatibility with legacy software.

Also tweak a few things for compatilibility. cryptography.io expects
SSL_CTX_set_read_ahead to return something, SSL_get_server_tmp_key's
signature was wrong, and cryptography.io tries to redefine
SSL_get_server_tmp_key if SSL_CTRL_GET_SERVER_TMP_KEY is missing.

Change-Id: I2f99711783456bfb7324e9ad972510be8a95e845
Reviewed-on: https://boringssl-review.googlesource.com/28404
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata
index 6cedfe2..7efbcd3 100644
--- a/crypto/err/ssl.errordata
+++ b/crypto/err/ssl.errordata
@@ -108,6 +108,7 @@
 SSL,280,NO_SUPPORTED_VERSIONS_ENABLED
 SSL,185,NULL_SSL_CTX
 SSL,186,NULL_SSL_METHOD_PASSED
+SSL,289,OCSP_CB_ERROR
 SSL,187,OLD_SESSION_CIPHER_NOT_RETURNED
 SSL,268,OLD_SESSION_PRF_HASH_MISMATCH
 SSL,188,OLD_SESSION_VERSION_NOT_RETURNED
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 187eb74..81a45c7 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -3802,14 +3802,14 @@
 // SSL_CTX_get_read_ahead returns zero.
 OPENSSL_EXPORT int SSL_CTX_get_read_ahead(const SSL_CTX *ctx);
 
-// SSL_CTX_set_read_ahead does nothing.
-OPENSSL_EXPORT void SSL_CTX_set_read_ahead(SSL_CTX *ctx, int yes);
+// SSL_CTX_set_read_ahead returns one.
+OPENSSL_EXPORT int SSL_CTX_set_read_ahead(SSL_CTX *ctx, int yes);
 
 // SSL_get_read_ahead returns zero.
 OPENSSL_EXPORT int SSL_get_read_ahead(const SSL *ssl);
 
-// SSL_set_read_ahead does nothing.
-OPENSSL_EXPORT void SSL_set_read_ahead(SSL *ssl, int yes);
+// SSL_set_read_ahead returns one.
+OPENSSL_EXPORT int SSL_set_read_ahead(SSL *ssl, int yes);
 
 // SSL_renegotiate put an error on the error queue and returns zero.
 OPENSSL_EXPORT int SSL_renegotiate(SSL *ssl);
@@ -3880,7 +3880,7 @@
 OPENSSL_EXPORT const COMP_METHOD *SSL_get_current_expansion(SSL *ssl);
 
 // SSL_get_server_tmp_key returns zero.
-OPENSSL_EXPORT int *SSL_get_server_tmp_key(SSL *ssl, EVP_PKEY **out_key);
+OPENSSL_EXPORT int SSL_get_server_tmp_key(SSL *ssl, EVP_PKEY **out_key);
 
 // SSL_CTX_set_tmp_dh returns 1.
 OPENSSL_EXPORT int SSL_CTX_set_tmp_dh(SSL_CTX *ctx, const DH *dh);
@@ -4190,6 +4190,58 @@
 #define SSL_SIGN_RSA_PSS_SHA384 SSL_SIGN_RSA_PSS_RSAE_SHA384
 #define SSL_SIGN_RSA_PSS_SHA512 SSL_SIGN_RSA_PSS_RSAE_SHA512
 
+// SSL_set_tlsext_status_type configures a client to request OCSP stapling if
+// |type| is |TLSEXT_STATUSTYPE_ocsp| and disables it otherwise. It returns one
+// on success and zero if handshake configuration has already been shed.
+//
+// Use |SSL_enable_ocsp_stapling| instead.
+OPENSSL_EXPORT int SSL_set_tlsext_status_type(SSL *ssl, int type);
+
+// SSL_set_tlsext_status_ocsp_resp sets the OCSP response. It returns one on
+// success and zero on error. On success, |ssl| takes ownership of |resp|, which
+// must have been allocated by |OPENSSL_malloc|.
+//
+// Use |SSL_set_ocsp_response| instead.
+OPENSSL_EXPORT int SSL_set_tlsext_status_ocsp_resp(SSL *ssl, uint8_t *resp,
+                                                   size_t resp_len);
+
+// SSL_get_tlsext_status_ocsp_resp sets |*out| to point to the OCSP response
+// from the server. It returns the length of the response. If there was no
+// response, it sets |*out| to NULL and returns zero.
+//
+// Use |SSL_get0_ocsp_response| instead.
+//
+// WARNING: the returned data is not guaranteed to be well formed.
+OPENSSL_EXPORT size_t SSL_get_tlsext_status_ocsp_resp(const SSL *ssl,
+                                                      const uint8_t **out);
+
+// SSL_CTX_set_tlsext_status_cb configures the legacy OpenSSL OCSP callback and
+// returns one. Though the type signature is the same, this callback has
+// different behavior for client and server connections:
+//
+// For clients, the callback is called after certificate verification. It should
+// return one for success, zero for a bad OCSP response, and a negative number
+// for internal error. Instead, handle this as part of certificate verification.
+// (Historically, OpenSSL verified certificates just before parsing stapled OCSP
+// responses, but BoringSSL fixes this ordering. All server credentials are
+// available during verification.)
+//
+// Do not use this callback as a server. It is provided for compatibility
+// purposes only. For servers, it is called to configure server credentials. It
+// should return |SSL_TLSEXT_ERR_OK| on success, |SSL_TLSEXT_ERR_NOACK| to
+// ignore OCSP requests, or |SSL_TLSEXT_ERR_ALERT_FATAL| on error. It is usually
+// used to fetch OCSP responses on demand, which is not ideal. Instead, treat
+// OCSP responses like other server credentials, such as certificates or SCT
+// lists. Configure, store, and refresh them eagerly. This avoids downtime if
+// the CA's OCSP responder is briefly offline.
+OPENSSL_EXPORT int SSL_CTX_set_tlsext_status_cb(SSL_CTX *ctx,
+                                                int (*callback)(SSL *ssl,
+                                                                void *arg));
+
+// SSL_CTX_set_tlsext_status_arg sets additional data for
+// |SSL_CTX_set_tlsext_status_cb|'s callback and returns one.
+OPENSSL_EXPORT int SSL_CTX_set_tlsext_status_arg(SSL_CTX *ctx, void *arg);
+
 
 // Private structures.
 //
@@ -4367,6 +4419,7 @@
 #define SSL_CTRL_GET_NUM_RENEGOTIATIONS doesnt_exist
 #define SSL_CTRL_GET_READ_AHEAD doesnt_exist
 #define SSL_CTRL_GET_RI_SUPPORT doesnt_exist
+#define SSL_CTRL_GET_SERVER_TMP_KEY doesnt_exist
 #define SSL_CTRL_GET_SESSION_REUSED doesnt_exist
 #define SSL_CTRL_GET_SESS_CACHE_MODE doesnt_exist
 #define SSL_CTRL_GET_SESS_CACHE_SIZE doesnt_exist
@@ -4782,6 +4835,7 @@
 #define SSL_R_SERVER_ECHOED_INVALID_SESSION_ID 286
 #define SSL_R_PRIVATE_KEY_OPERATION_FAILED 287
 #define SSL_R_SECOND_SERVERHELLO_VERSION_MISMATCH 288
+#define SSL_R_OCSP_CB_ERROR 289
 #define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000
 #define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010
 #define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h
index 2aa018b..4b25806 100644
--- a/include/openssl/tls1.h
+++ b/include/openssl/tls1.h
@@ -238,6 +238,7 @@
 #define TLSEXT_TYPE_dummy_pq_padding 54537
 
 // status request value from RFC 3546
+#define TLSEXT_STATUSTYPE_nothing (-1)
 #define TLSEXT_STATUSTYPE_ocsp 1
 
 // ECPointFormat values from RFC 4492
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
index 3962618..9cad971 100644
--- a/ssl/handshake.cc
+++ b/ssl/handshake.cc
@@ -364,6 +364,22 @@
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
   }
 
+  // Emulate OpenSSL's client OCSP callback. OpenSSL verifies certificates
+  // before it receives the OCSP, so it needs a second callback for OCSP.
+  if (ret == ssl_verify_ok && !ssl->server &&
+      hs->new_session->ocsp_response != nullptr &&
+      ssl->ctx->legacy_ocsp_callback != nullptr) {
+    int cb_ret =
+        ssl->ctx->legacy_ocsp_callback(ssl, ssl->ctx->legacy_ocsp_callback_arg);
+    if (cb_ret <= 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_OCSP_CB_ERROR);
+      ssl_send_alert(ssl, SSL3_AL_FATAL,
+                     cb_ret == 0 ? SSL_AD_BAD_CERTIFICATE_STATUS_RESPONSE
+                                 : SSL_AD_INTERNAL_ERROR);
+      ret = ssl_verify_invalid;
+    }
+  }
+
   return ret;
 }
 
diff --git a/ssl/handshake_server.cc b/ssl/handshake_server.cc
index ac8d46b..3c06b33 100644
--- a/ssl/handshake_server.cc
+++ b/ssl/handshake_server.cc
@@ -509,6 +509,22 @@
     return ssl_hs_error;
   }
 
+  if (hs->ocsp_stapling_requested &&
+      ssl->ctx->legacy_ocsp_callback != nullptr) {
+    switch (ssl->ctx->legacy_ocsp_callback(
+        ssl, ssl->ctx->legacy_ocsp_callback_arg)) {
+      case SSL_TLSEXT_ERR_OK:
+        break;
+      case SSL_TLSEXT_ERR_NOACK:
+        hs->ocsp_stapling_requested = false;
+        break;
+      default:
+        OPENSSL_PUT_ERROR(SSL, SSL_R_OCSP_CB_ERROR);
+        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+        return ssl_hs_error;
+    }
+  }
+
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     // Jump to the TLS 1.3 state machine.
     hs->state = state12_tls13;
diff --git a/ssl/internal.h b/ssl/internal.h
index be1b9dd..28ea87b 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -2213,6 +2213,11 @@
   // session tickets.
   const SSL_TICKET_AEAD_METHOD *ticket_aead_method;
 
+  // legacy_ocsp_callback implements an OCSP-related callback for OpenSSL
+  // compatibility.
+  int (*legacy_ocsp_callback)(SSL *ssl, void *arg);
+  void *legacy_ocsp_callback_arg;
+
   // verify_sigalgs, if not empty, is the set of signature algorithms
   // accepted from the peer in decreasing order of preference.
   uint16_t *verify_sigalgs;
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 8fb9ada..606d1fc 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -1664,9 +1664,9 @@
 
 int SSL_get_read_ahead(const SSL *ssl) { return 0; }
 
-void SSL_CTX_set_read_ahead(SSL_CTX *ctx, int yes) { }
+int SSL_CTX_set_read_ahead(SSL_CTX *ctx, int yes) { return 1; }
 
-void SSL_set_read_ahead(SSL *ssl, int yes) { }
+int SSL_set_read_ahead(SSL *ssl, int yes) { return 1; }
 
 int SSL_pending(const SSL *ssl) {
   return static_cast<int>(ssl->s3->pending_app_data.size());
@@ -2321,7 +2321,7 @@
 
 const COMP_METHOD *SSL_get_current_expansion(SSL *ssl) { return NULL; }
 
-int *SSL_get_server_tmp_key(SSL *ssl, EVP_PKEY **out_key) { return 0; }
+int SSL_get_server_tmp_key(SSL *ssl, EVP_PKEY **out_key) { return 0; }
 
 void SSL_CTX_set_quiet_shutdown(SSL_CTX *ctx, int mode) {
   ctx->quiet_shutdown = (mode != 0);
@@ -2872,3 +2872,36 @@
                                     const SSL_TICKET_AEAD_METHOD *aead_method) {
   ctx->ticket_aead_method = aead_method;
 }
+
+int SSL_set_tlsext_status_type(SSL *ssl, int type) {
+  if (!ssl->config) {
+    return 0;
+  }
+  ssl->config->ocsp_stapling_enabled = type == TLSEXT_STATUSTYPE_ocsp;
+  return 1;
+}
+
+int SSL_set_tlsext_status_ocsp_resp(SSL *ssl, uint8_t *resp, size_t resp_len) {
+  if (SSL_set_ocsp_response(ssl, resp, resp_len)) {
+    OPENSSL_free(resp);
+    return 1;
+  }
+  return 0;
+}
+
+size_t SSL_get_tlsext_status_ocsp_resp(const SSL *ssl, const uint8_t **out) {
+  size_t ret;
+  SSL_get0_ocsp_response(ssl, out, &ret);
+  return ret;
+}
+
+int SSL_CTX_set_tlsext_status_cb(SSL_CTX *ctx,
+                                 int (*callback)(SSL *ssl, void *arg)) {
+  ctx->legacy_ocsp_callback = callback;
+  return 1;
+}
+
+int SSL_CTX_set_tlsext_status_arg(SSL_CTX *ctx, void *arg) {
+  ctx->legacy_ocsp_callback_arg = arg;
+  return 1;
+}
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 08206d3..1d15771 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -466,6 +466,7 @@
     return false;
   }
   if (!config->ocsp_response.empty() &&
+      !config->set_ocsp_in_callback &&
       !SSL_set_ocsp_response(ssl, (const uint8_t *)config->ocsp_response.data(),
                              config->ocsp_response.size())) {
     return false;
@@ -1071,6 +1072,27 @@
   }
 }
 
+static int LegacyOCSPCallback(SSL *ssl, void *arg) {
+  const TestConfig *config = GetTestConfig(ssl);
+  if (!SSL_is_server(ssl)) {
+    return !config->fail_ocsp_callback;
+  }
+
+  if (!config->ocsp_response.empty() &&
+      config->set_ocsp_in_callback &&
+      !SSL_set_ocsp_response(ssl, (const uint8_t *)config->ocsp_response.data(),
+                             config->ocsp_response.size())) {
+    return SSL_TLSEXT_ERR_ALERT_FATAL;
+  }
+  if (config->fail_ocsp_callback) {
+    return SSL_TLSEXT_ERR_ALERT_FATAL;
+  }
+  if (config->decline_ocsp_callback) {
+    return SSL_TLSEXT_ERR_NOACK;
+  }
+  return SSL_TLSEXT_ERR_OK;
+}
+
 // Connect returns a new socket connected to localhost on |port| or -1 on
 // error.
 static int Connect(uint16_t port) {
@@ -1308,6 +1330,10 @@
     SSL_CTX_set_false_start_allowed_without_alpn(ssl_ctx.get(), 1);
   }
 
+  if (config->use_ocsp_callback) {
+    SSL_CTX_set_tlsext_status_cb(ssl_ctx.get(), LegacyOCSPCallback);
+  }
+
   if (old_ctx) {
     uint8_t keys[48];
     if (!SSL_CTX_get_tlsext_ticket_keys(old_ctx, &keys, sizeof(keys)) ||
diff --git a/ssl/test/runner/alert.go b/ssl/test/runner/alert.go
index 652e9ee..c79725e 100644
--- a/ssl/test/runner/alert.go
+++ b/ssl/test/runner/alert.go
@@ -15,69 +15,71 @@
 )
 
 const (
-	alertCloseNotify            alert = 0
-	alertEndOfEarlyData         alert = 1
-	alertUnexpectedMessage      alert = 10
-	alertBadRecordMAC           alert = 20
-	alertDecryptionFailed       alert = 21
-	alertRecordOverflow         alert = 22
-	alertDecompressionFailure   alert = 30
-	alertHandshakeFailure       alert = 40
-	alertNoCertificate          alert = 41
-	alertBadCertificate         alert = 42
-	alertUnsupportedCertificate alert = 43
-	alertCertificateRevoked     alert = 44
-	alertCertificateExpired     alert = 45
-	alertCertificateUnknown     alert = 46
-	alertIllegalParameter       alert = 47
-	alertUnknownCA              alert = 48
-	alertAccessDenied           alert = 49
-	alertDecodeError            alert = 50
-	alertDecryptError           alert = 51
-	alertProtocolVersion        alert = 70
-	alertInsufficientSecurity   alert = 71
-	alertInternalError          alert = 80
-	alertInappropriateFallback  alert = 86
-	alertUserCanceled           alert = 90
-	alertNoRenegotiation        alert = 100
-	alertMissingExtension       alert = 109
-	alertUnsupportedExtension   alert = 110
-	alertUnrecognizedName       alert = 112
-	alertUnknownPSKIdentity     alert = 115
-	alertCertificateRequired    alert = 116
+	alertCloseNotify                  alert = 0
+	alertEndOfEarlyData               alert = 1
+	alertUnexpectedMessage            alert = 10
+	alertBadRecordMAC                 alert = 20
+	alertDecryptionFailed             alert = 21
+	alertRecordOverflow               alert = 22
+	alertDecompressionFailure         alert = 30
+	alertHandshakeFailure             alert = 40
+	alertNoCertificate                alert = 41
+	alertBadCertificate               alert = 42
+	alertUnsupportedCertificate       alert = 43
+	alertCertificateRevoked           alert = 44
+	alertCertificateExpired           alert = 45
+	alertCertificateUnknown           alert = 46
+	alertIllegalParameter             alert = 47
+	alertUnknownCA                    alert = 48
+	alertAccessDenied                 alert = 49
+	alertDecodeError                  alert = 50
+	alertDecryptError                 alert = 51
+	alertProtocolVersion              alert = 70
+	alertInsufficientSecurity         alert = 71
+	alertInternalError                alert = 80
+	alertInappropriateFallback        alert = 86
+	alertUserCanceled                 alert = 90
+	alertNoRenegotiation              alert = 100
+	alertMissingExtension             alert = 109
+	alertUnsupportedExtension         alert = 110
+	alertUnrecognizedName             alert = 112
+	alertBadCertificateStatusResponse alert = 113
+	alertUnknownPSKIdentity           alert = 115
+	alertCertificateRequired          alert = 116
 )
 
 var alertText = map[alert]string{
-	alertCloseNotify:            "close notify",
-	alertEndOfEarlyData:         "end of early data",
-	alertUnexpectedMessage:      "unexpected message",
-	alertBadRecordMAC:           "bad record MAC",
-	alertDecryptionFailed:       "decryption failed",
-	alertRecordOverflow:         "record overflow",
-	alertDecompressionFailure:   "decompression failure",
-	alertHandshakeFailure:       "handshake failure",
-	alertNoCertificate:          "no certificate",
-	alertBadCertificate:         "bad certificate",
-	alertUnsupportedCertificate: "unsupported certificate",
-	alertCertificateRevoked:     "revoked certificate",
-	alertCertificateExpired:     "expired certificate",
-	alertCertificateUnknown:     "unknown certificate",
-	alertIllegalParameter:       "illegal parameter",
-	alertUnknownCA:              "unknown certificate authority",
-	alertAccessDenied:           "access denied",
-	alertDecodeError:            "error decoding message",
-	alertDecryptError:           "error decrypting message",
-	alertProtocolVersion:        "protocol version not supported",
-	alertInsufficientSecurity:   "insufficient security level",
-	alertInternalError:          "internal error",
-	alertInappropriateFallback:  "inappropriate fallback",
-	alertUserCanceled:           "user canceled",
-	alertNoRenegotiation:        "no renegotiation",
-	alertMissingExtension:       "missing extension",
-	alertUnsupportedExtension:   "unsupported extension",
-	alertUnrecognizedName:       "unrecognized name",
-	alertUnknownPSKIdentity:     "unknown PSK identity",
-	alertCertificateRequired:    "certificate required",
+	alertCloseNotify:                  "close notify",
+	alertEndOfEarlyData:               "end of early data",
+	alertUnexpectedMessage:            "unexpected message",
+	alertBadRecordMAC:                 "bad record MAC",
+	alertDecryptionFailed:             "decryption failed",
+	alertRecordOverflow:               "record overflow",
+	alertDecompressionFailure:         "decompression failure",
+	alertHandshakeFailure:             "handshake failure",
+	alertNoCertificate:                "no certificate",
+	alertBadCertificate:               "bad certificate",
+	alertUnsupportedCertificate:       "unsupported certificate",
+	alertCertificateRevoked:           "revoked certificate",
+	alertCertificateExpired:           "expired certificate",
+	alertCertificateUnknown:           "unknown certificate",
+	alertIllegalParameter:             "illegal parameter",
+	alertUnknownCA:                    "unknown certificate authority",
+	alertAccessDenied:                 "access denied",
+	alertDecodeError:                  "error decoding message",
+	alertDecryptError:                 "error decrypting message",
+	alertProtocolVersion:              "protocol version not supported",
+	alertInsufficientSecurity:         "insufficient security level",
+	alertInternalError:                "internal error",
+	alertInappropriateFallback:        "inappropriate fallback",
+	alertUserCanceled:                 "user canceled",
+	alertNoRenegotiation:              "no renegotiation",
+	alertMissingExtension:             "missing extension",
+	alertUnsupportedExtension:         "unsupported extension",
+	alertBadCertificateStatusResponse: "bad certificate status response",
+	alertUnrecognizedName:             "unrecognized name",
+	alertUnknownPSKIdentity:           "unknown PSK identity",
+	alertCertificateRequired:          "certificate required",
 }
 
 func (e alert) String() string {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 53eeb8b..7e9a6ef 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -4766,60 +4766,157 @@
 	})
 
 	// OCSP stapling tests.
-	tests = append(tests, testCase{
-		testType: clientTest,
-		name:     "OCSPStapling-Client",
-		config: Config{
-			MaxVersion: VersionTLS12,
-		},
-		flags: []string{
-			"-enable-ocsp-stapling",
-			"-expect-ocsp-response",
-			base64.StdEncoding.EncodeToString(testOCSPResponse),
-			"-verify-peer",
-		},
-		resumeSession: true,
-	})
-	tests = append(tests, testCase{
-		testType: serverTest,
-		name:     "OCSPStapling-Server",
-		config: Config{
-			MaxVersion: VersionTLS12,
-		},
-		expectedOCSPResponse: testOCSPResponse,
-		flags: []string{
-			"-ocsp-response",
-			base64.StdEncoding.EncodeToString(testOCSPResponse),
-		},
-		resumeSession: true,
-	})
-	tests = append(tests, testCase{
-		testType: clientTest,
-		name:     "OCSPStapling-Client-TLS13",
-		config: Config{
-			MaxVersion: VersionTLS13,
-		},
-		flags: []string{
-			"-enable-ocsp-stapling",
-			"-expect-ocsp-response",
-			base64.StdEncoding.EncodeToString(testOCSPResponse),
-			"-verify-peer",
-		},
-		resumeSession: true,
-	})
-	tests = append(tests, testCase{
-		testType: serverTest,
-		name:     "OCSPStapling-Server-TLS13",
-		config: Config{
-			MaxVersion: VersionTLS13,
-		},
-		expectedOCSPResponse: testOCSPResponse,
-		flags: []string{
-			"-ocsp-response",
-			base64.StdEncoding.EncodeToString(testOCSPResponse),
-		},
-		resumeSession: true,
-	})
+	for _, vers := range tlsVersions {
+		if config.protocol == dtls && !vers.hasDTLS {
+			continue
+		}
+		if vers.version == VersionSSL30 {
+			continue
+		}
+		tests = append(tests, testCase{
+			testType: clientTest,
+			name:     "OCSPStapling-Client-" + vers.name,
+			config: Config{
+				MaxVersion: vers.version,
+			},
+			tls13Variant: vers.tls13Variant,
+			flags: []string{
+				"-enable-ocsp-stapling",
+				"-expect-ocsp-response",
+				base64.StdEncoding.EncodeToString(testOCSPResponse),
+				"-verify-peer",
+			},
+			resumeSession: true,
+		})
+		tests = append(tests, testCase{
+			testType: serverTest,
+			name:     "OCSPStapling-Server-" + vers.name,
+			config: Config{
+				MaxVersion: vers.version,
+			},
+			tls13Variant:         vers.tls13Variant,
+			expectedOCSPResponse: testOCSPResponse,
+			flags: []string{
+				"-ocsp-response",
+				base64.StdEncoding.EncodeToString(testOCSPResponse),
+			},
+			resumeSession: true,
+		})
+
+		// The client OCSP callback is an alternate certificate
+		// verification callback.
+		tests = append(tests, testCase{
+			testType: clientTest,
+			name:     "ClientOCSPCallback-Pass-" + vers.name,
+			config: Config{
+				MaxVersion:   vers.version,
+				Certificates: []Certificate{rsaCertificate},
+			},
+			tls13Variant: vers.tls13Variant,
+			flags: []string{
+				"-enable-ocsp-stapling",
+				"-use-ocsp-callback",
+			},
+		})
+		var expectedLocalError string
+		if !config.async {
+			// TODO(davidben): Asynchronous fatal alerts are never
+			// sent. https://crbug.com/boringssl/130.
+			expectedLocalError = "remote error: bad certificate status response"
+		}
+		tests = append(tests, testCase{
+			testType: clientTest,
+			name:     "ClientOCSPCallback-Fail-" + vers.name,
+			config: Config{
+				MaxVersion:   vers.version,
+				Certificates: []Certificate{rsaCertificate},
+			},
+			tls13Variant: vers.tls13Variant,
+			flags: []string{
+				"-enable-ocsp-stapling",
+				"-use-ocsp-callback",
+				"-fail-ocsp-callback",
+			},
+			shouldFail:         true,
+			expectedLocalError: expectedLocalError,
+			expectedError:      ":OCSP_CB_ERROR:",
+		})
+		// The callback does not run if the server does not send an
+		// OCSP response.
+		certNoStaple := rsaCertificate
+		certNoStaple.OCSPStaple = nil
+		tests = append(tests, testCase{
+			testType: clientTest,
+			name:     "ClientOCSPCallback-FailNoStaple-" + vers.name,
+			config: Config{
+				MaxVersion:   vers.version,
+				Certificates: []Certificate{certNoStaple},
+			},
+			tls13Variant: vers.tls13Variant,
+			flags: []string{
+				"-enable-ocsp-stapling",
+				"-use-ocsp-callback",
+				"-fail-ocsp-callback",
+			},
+		})
+
+		// The server OCSP callback is a legacy mechanism for
+		// configuring OCSP, used by unreliable server software.
+		tests = append(tests, testCase{
+			testType: serverTest,
+			name:     "ServerOCSPCallback-SetInCallback-" + vers.name,
+			config: Config{
+				MaxVersion: vers.version,
+			},
+			tls13Variant:         vers.tls13Variant,
+			expectedOCSPResponse: testOCSPResponse,
+			flags: []string{
+				"-use-ocsp-callback",
+				"-set-ocsp-in-callback",
+				"-ocsp-response",
+				base64.StdEncoding.EncodeToString(testOCSPResponse),
+			},
+			resumeSession: true,
+		})
+
+		// The callback may decline OCSP, in which case  we act as if
+		// the client did not support it, even if a response was
+		// configured.
+		tests = append(tests, testCase{
+			testType: serverTest,
+			name:     "ServerOCSPCallback-Decline-" + vers.name,
+			config: Config{
+				MaxVersion: vers.version,
+			},
+			tls13Variant:         vers.tls13Variant,
+			expectedOCSPResponse: []byte{},
+			flags: []string{
+				"-use-ocsp-callback",
+				"-decline-ocsp-callback",
+				"-ocsp-response",
+				base64.StdEncoding.EncodeToString(testOCSPResponse),
+			},
+			resumeSession: true,
+		})
+
+		// The callback may also signal an internal error.
+		tests = append(tests, testCase{
+			testType: serverTest,
+			name:     "ServerOCSPCallback-Fail-" + vers.name,
+			config: Config{
+				MaxVersion: vers.version,
+			},
+			tls13Variant: vers.tls13Variant,
+			flags: []string{
+				"-use-ocsp-callback",
+				"-fail-ocsp-callback",
+				"-ocsp-response",
+				base64.StdEncoding.EncodeToString(testOCSPResponse),
+			},
+			shouldFail:    true,
+			expectedError: ":OCSP_CB_ERROR:",
+		})
+	}
 
 	// Certificate verification tests.
 	for _, vers := range tlsVersions {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 2ab5bfe..c5ce610 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -136,6 +136,10 @@
   { "-handoff", &TestConfig::handoff },
   { "-expect-dummy-pq-padding", &TestConfig::expect_dummy_pq_padding },
   { "-no-rsa-pss-rsae-certs", &TestConfig::no_rsa_pss_rsae_certs },
+  { "-use-ocsp-callback", &TestConfig::use_ocsp_callback },
+  { "-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback },
+  { "-decline-ocsp-callback", &TestConfig::decline_ocsp_callback },
+  { "-fail-ocsp-callback", &TestConfig::fail_ocsp_callback },
 };
 
 const Flag<std::string> kStringFlags[] = {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 9a026ce..57c4ced 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -156,6 +156,10 @@
   bool handoff = false;
   bool expect_dummy_pq_padding = false;
   bool no_rsa_pss_rsae_certs = false;
+  bool use_ocsp_callback = false;
+  bool set_ocsp_in_callback = false;
+  bool decline_ocsp_callback = false;
+  bool fail_ocsp_callback = false;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_initial,