Server-side OCSP stapling support.
This is a simpler implementation than OpenSSL's, lacking responder IDs
and request extensions support. This mirrors the client implementation
already present.
Change-Id: I54592b60e0a708bfb003d491c9250401403c9e69
Reviewed-on: https://boringssl-review.googlesource.com/5700
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 7d62fb9..8397b74 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -627,6 +627,13 @@
OPENSSL_EXPORT int SSL_get0_chain_certs(const SSL *ssl,
STACK_OF(X509) **out_chain);
+/* SSL_CTX_set_ocsp_response sets the OCSP reponse that is sent to clients
+ * which request it. It returns one on success and zero on error. The caller
+ * retains ownership of |response|. */
+OPENSSL_EXPORT int SSL_CTX_set_ocsp_response(SSL_CTX *ctx,
+ const uint8_t *response,
+ size_t response_len);
+
/* Certificate and private key convenience functions. */
@@ -1464,6 +1471,10 @@
/* If true, a client will request a stapled OCSP response. */
char ocsp_stapling_enabled;
+ /* OCSP response to be sent to the client, if requested. */
+ uint8_t *ocsp_response;
+ size_t ocsp_response_length;
+
/* If not NULL, session key material will be logged to this BIO for debugging
* purposes. The format matches NSS's and is readable by Wireshark. */
BIO *keylog_bio;
@@ -1830,9 +1841,8 @@
/* Enable signed certificate time stamps. Currently client only. */
char signed_cert_timestamps_enabled;
- /* Enable OCSP stapling. Currently client only.
- * TODO(davidben): Add a server-side implementation when it becomes
- * necesary. */
+ /* ocsp_stapling_enabled is only used by client connections and indicates
+ * whether OCSP stapling will be requested. */
char ocsp_stapling_enabled;
/* For a client, this contains the list of supported protocols in wire
diff --git a/include/openssl/ssl3.h b/include/openssl/ssl3.h
index 1c44248..7ff8dbd 100644
--- a/include/openssl/ssl3.h
+++ b/include/openssl/ssl3.h
@@ -491,8 +491,12 @@
int cert_request;
/* certificate_status_expected is true if OCSP stapling was negotiated and
- * the server is expected to send a CertificateStatus message. */
- char certificate_status_expected;
+ * the server is expected to send a CertificateStatus message. (This is
+ * used on both the client and server sides.) */
+ unsigned certificate_status_expected:1;
+
+ /* ocsp_stapling_requested is true if a client requested OCSP stapling. */
+ unsigned ocsp_stapling_requested:1;
/* Server-only: peer_ellipticcurvelist contains the EC curve IDs advertised
* by the peer. This is only set on the server's end. The server does not
diff --git a/ssl/d1_srvr.c b/ssl/d1_srvr.c
index 3dd7701..ca08651 100644
--- a/ssl/d1_srvr.c
+++ b/ssl/d1_srvr.c
@@ -239,6 +239,16 @@
s->init_num = 0;
break;
+ case SSL3_ST_SW_CERT_STATUS_A:
+ case SSL3_ST_SW_CERT_STATUS_B:
+ ret = ssl3_send_certificate_status(s);
+ if (ret <= 0) {
+ goto end;
+ }
+ s->state = SSL3_ST_SW_KEY_EXCH_A;
+ s->init_num = 0;
+ break;
+
case SSL3_ST_SW_KEY_EXCH_A:
case SSL3_ST_SW_KEY_EXCH_B:
case SSL3_ST_SW_KEY_EXCH_C:
diff --git a/ssl/internal.h b/ssl/internal.h
index 40ca00b..6781e26 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -880,7 +880,7 @@
int ssl3_send_server_certificate(SSL *s);
int ssl3_send_new_session_ticket(SSL *s);
-int ssl3_send_cert_status(SSL *s);
+int ssl3_send_certificate_status(SSL *s);
int ssl3_get_finished(SSL *s, int state_a, int state_b);
int ssl3_send_change_cipher_spec(SSL *s, int state_a, int state_b);
int ssl3_prf(SSL *s, uint8_t *out, size_t out_len, const uint8_t *secret,
diff --git a/ssl/s3_srvr.c b/ssl/s3_srvr.c
index 8045260..5a46abc 100644
--- a/ssl/s3_srvr.c
+++ b/ssl/s3_srvr.c
@@ -306,6 +306,16 @@
s->init_num = 0;
break;
+ case SSL3_ST_SW_CERT_STATUS_A:
+ case SSL3_ST_SW_CERT_STATUS_B:
+ ret = ssl3_send_certificate_status(s);
+ if (ret <= 0) {
+ goto end;
+ }
+ s->state = SSL3_ST_SW_KEY_EXCH_A;
+ s->init_num = 0;
+ break;
+
case SSL3_ST_SW_KEY_EXCH_A:
case SSL3_ST_SW_KEY_EXCH_B:
case SSL3_ST_SW_KEY_EXCH_C:
@@ -1191,6 +1201,32 @@
return ssl_do_write(s);
}
+int ssl3_send_certificate_status(SSL *ssl) {
+ if (ssl->state == SSL3_ST_SW_CERT_STATUS_A) {
+ CBB out, ocsp_response;
+ size_t length;
+
+ CBB_zero(&out);
+ if (!CBB_init_fixed(&out, ssl_handshake_start(ssl),
+ ssl->init_buf->max - SSL_HM_HEADER_LENGTH(ssl)) ||
+ !CBB_add_u8(&out, TLSEXT_STATUSTYPE_ocsp) ||
+ !CBB_add_u24_length_prefixed(&out, &ocsp_response) ||
+ !CBB_add_bytes(&ocsp_response, ssl->ctx->ocsp_response,
+ ssl->ctx->ocsp_response_length) ||
+ !CBB_finish(&out, NULL, &length) ||
+ !ssl_set_handshake_header(ssl, SSL3_MT_CERTIFICATE_STATUS, length)) {
+ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+ CBB_cleanup(&out);
+ return -1;
+ }
+
+ ssl->state = SSL3_ST_SW_CERT_STATUS_B;
+ }
+
+ /* SSL3_ST_SW_CERT_STATUS_B */
+ return ssl_do_write(ssl);
+}
+
int ssl3_send_server_done(SSL *s) {
if (s->state == SSL3_ST_SW_SRVR_DONE_A) {
if (!ssl_set_handshake_header(s, SSL3_MT_SERVER_DONE, 0)) {
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index e2437b9..988b3a1 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -1385,6 +1385,20 @@
*out_len = session->ocsp_response_length;
}
+int SSL_CTX_set_ocsp_response(SSL_CTX *ctx, const uint8_t *response,
+ size_t response_len) {
+ OPENSSL_free(ctx->ocsp_response);
+ ctx->ocsp_response_length = 0;
+
+ ctx->ocsp_response = BUF_memdup(response, response_len);
+ if (ctx->ocsp_response == NULL) {
+ return 0;
+ }
+ ctx->ocsp_response_length = response_len;
+
+ return 1;
+}
+
/* SSL_select_next_proto implements the standard protocol selection. It is
* expected that this function is called from the callback set by
* SSL_CTX_set_next_proto_select_cb.
@@ -1753,6 +1767,7 @@
OPENSSL_free(ctx->psk_identity_hint);
OPENSSL_free(ctx->tlsext_ellipticcurvelist);
OPENSSL_free(ctx->alpn_client_proto_list);
+ OPENSSL_free(ctx->ocsp_response);
EVP_PKEY_free(ctx->tlsext_channel_id_private);
BIO_free(ctx->keylog_bio);
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index 3902f8f..2eeffab 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -1330,7 +1330,7 @@
}
static int ext_ocsp_parse_serverhello(SSL *ssl, uint8_t *out_alert,
- CBS *contents) {
+ CBS *contents) {
if (contents == NULL) {
return 1;
}
@@ -1345,13 +1345,32 @@
static int ext_ocsp_parse_clienthello(SSL *ssl, uint8_t *out_alert,
CBS *contents) {
- /* OCSP stapling as a server is not supported. */
+ if (contents == NULL) {
+ return 1;
+ }
+
+ uint8_t status_type;
+ if (!CBS_get_u8(contents, &status_type)) {
+ return 0;
+ }
+
+ /* We cannot decide whether OCSP stapling will occur yet because the correct
+ * SSL_CTX might not have been selected. */
+ ssl->s3->tmp.ocsp_stapling_requested = status_type == TLSEXT_STATUSTYPE_ocsp;
+
return 1;
}
static int ext_ocsp_add_serverhello(SSL *ssl, CBB *out) {
- /* OCSP stapling as a server is not supported. */
- return 1;
+ if (!ssl->s3->tmp.ocsp_stapling_requested ||
+ ssl->ctx->ocsp_response_length == 0) {
+ return 1;
+ }
+
+ ssl->s3->tmp.certificate_status_expected = 1;
+
+ return CBB_add_u16(out, TLSEXT_TYPE_status_request) &&
+ CBB_add_u16(out, 0 /* length */);
}
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 53f8e29..d839f5f 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -240,6 +240,12 @@
SSL_FILETYPE_PEM)) {
return false;
}
+ if (!config->ocsp_response.empty() &&
+ !SSL_CTX_set_ocsp_response(ssl->ctx,
+ (const uint8_t *)config->ocsp_response.data(),
+ config->ocsp_response.size())) {
+ return false;
+ }
return true;
}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 49ada2a..b379074 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -155,6 +155,8 @@
// expectedSRTPProtectionProfile is the DTLS-SRTP profile that
// should be negotiated. If zero, none should be negotiated.
expectedSRTPProtectionProfile uint16
+ // expectedOCSPResponse, if not nil, is the expected OCSP response to be received.
+ expectedOCSPResponse []uint8
// messageLen is the length, in bytes, of the test message that will be
// sent.
messageLen int
@@ -320,6 +322,10 @@
return fmt.Errorf("SRTP profile mismatch: got %d, wanted %d", p, test.expectedSRTPProtectionProfile)
}
+ if test.expectedOCSPResponse != nil && !bytes.Equal(test.expectedOCSPResponse, tlsConn.OCSPResponse()) {
+ return fmt.Errorf("OCSP Response mismatch")
+ }
+
if test.exportKeyingMaterial > 0 {
actual := make([]byte, test.exportKeyingMaterial)
if _, err := io.ReadFull(tlsConn, actual); err != nil {
@@ -2333,6 +2339,26 @@
flags: []string{"-psk", "secret"},
})
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "OCSPStapling-Client",
+ flags: []string{
+ "-enable-ocsp-stapling",
+ "-expect-ocsp-response",
+ base64.StdEncoding.EncodeToString(testOCSPResponse),
+ },
+ })
+
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "OCSPStapling-Server",
+ expectedOCSPResponse: testOCSPResponse,
+ flags: []string{
+ "-ocsp-response",
+ base64.StdEncoding.EncodeToString(testOCSPResponse),
+ },
+ })
+
if protocol == tls {
tests = append(tests, testCase{
name: "Renegotiate-Client",
@@ -3034,15 +3060,7 @@
shouldFail: true,
expectedError: ":BAD_SRTP_PROTECTION_PROFILE_LIST:",
})
- // Test OCSP stapling and SCT list.
- testCases = append(testCases, testCase{
- name: "OCSPStapling",
- flags: []string{
- "-enable-ocsp-stapling",
- "-expect-ocsp-response",
- base64.StdEncoding.EncodeToString(testOCSPResponse),
- },
- })
+ // Test SCT list.
testCases = append(testCases, testCase{
name: "SignedCertificateTimestampList",
flags: []string{
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index d873d8e..8c4b420 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -119,6 +119,7 @@
{ "-expect-ocsp-response", &TestConfig::expected_ocsp_response },
{ "-expect-signed-cert-timestamps",
&TestConfig::expected_signed_cert_timestamps },
+ { "-ocsp-response", &TestConfig::ocsp_response },
};
const Flag<int> kIntFlags[] = {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 29a1c77..4418ed3 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -86,6 +86,7 @@
bool enable_server_custom_extension = false;
bool custom_extension_skip = false;
bool custom_extension_fail_add = false;
+ std::string ocsp_response;
};
bool ParseConfig(int argc, char **argv, TestConfig *out_config);
diff --git a/tool/server.cc b/tool/server.cc
index 164d6a5..69994bc 100644
--- a/tool/server.cc
+++ b/tool/server.cc
@@ -35,10 +35,52 @@
"Private-key file to use (default is server.pem)",
},
{
+ "-ocsp-response", kOptionalArgument,
+ "OCSP response file to send",
+ },
+ {
"", kOptionalArgument, "",
},
};
+static bool LoadOCSPResponse(SSL_CTX *ctx, const char *filename) {
+ void *data = NULL;
+ bool ret = false;
+ long length;
+
+ FILE *f = fopen(filename, "rb");
+
+ if (f == NULL ||
+ fseek(f, 0, SEEK_END) != 0) {
+ goto out;
+ }
+
+ length = ftell(f);
+ if (length < 0) {
+ goto out;
+ }
+
+ data = malloc(length);
+ if (data == NULL) {
+ goto out;
+ }
+ rewind(f);
+
+ fread(data, 1, length, f);
+ if (ferror(f) != 0 ||
+ !SSL_CTX_set_ocsp_response(ctx, (uint8_t*)data, length)) {
+ goto out;
+ }
+
+ ret = true;
+out:
+ if (f != NULL) {
+ fclose(f);
+ }
+ free(data);
+ return ret;
+}
+
bool Server(const std::vector<std::string> &args) {
if (!InitSocketLibrary()) {
return false;
@@ -74,6 +116,12 @@
return false;
}
+ if (args_map.count("-ocsp-response") != 0 &&
+ !LoadOCSPResponse(ctx, args_map["-ocsp-response"].c_str())) {
+ fprintf(stderr, "Failed to load OCSP response: %s\n", args_map["-ocsp-response"].c_str());
+ return false;
+ }
+
int sock = -1;
if (!Accept(&sock, args_map["-accept"])) {
return false;