diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index 7d62fb99..8397b74c 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -627,6 +627,13 @@ OPENSSL_EXPORT int SSL_CTX_get_extra_chain_certs(const SSL_CTX *ctx, 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 @@ struct ssl_ctx_st { /* 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 @@ struct ssl_st { /* 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 1c442483..7ff8dbdd 100644 --- a/include/openssl/ssl3.h +++ b/include/openssl/ssl3.h @@ -491,8 +491,12 @@ typedef struct ssl3_state_st { 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 3dd77013..ca086512 100644 --- a/ssl/d1_srvr.c +++ b/ssl/d1_srvr.c @@ -239,6 +239,16 @@ int dtls1_accept(SSL *s) { 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 40ca00b7..6781e26d 100644 --- a/ssl/internal.h +++ b/ssl/internal.h @@ -880,7 +880,7 @@ int ssl_fill_hello_random(uint8_t *out, size_t len, int is_server); 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 8045260c..5a46abc8 100644 --- a/ssl/s3_srvr.c +++ b/ssl/s3_srvr.c @@ -306,6 +306,16 @@ int ssl3_accept(SSL *s) { 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 @@ int ssl3_send_server_hello(SSL *s) { 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 e2437b94..988b3a14 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -1385,6 +1385,20 @@ void SSL_get0_ocsp_response(const SSL *ssl, const uint8_t **out, *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 @@ void SSL_CTX_free(SSL_CTX *ctx) { 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 3902f8f0..2eeffabd 100644 --- a/ssl/t1_lib.c +++ b/ssl/t1_lib.c @@ -1330,7 +1330,7 @@ static int ext_ocsp_add_clienthello(SSL *ssl, CBB *out) { } 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_serverhello(SSL *ssl, uint8_t *out_alert, 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 53f8e291..d839f5f7 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc @@ -240,6 +240,12 @@ static bool InstallCertificate(SSL *ssl) { 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 49ada2ae..b379074a 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -155,6 +155,8 @@ type testCase struct { // 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 @@ func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool) er 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 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) 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 @@ func addExtensionTests() { 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 d873d8e5..8c4b420a 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc @@ -119,6 +119,7 @@ const Flag kBase64Flags[] = { { "-expect-ocsp-response", &TestConfig::expected_ocsp_response }, { "-expect-signed-cert-timestamps", &TestConfig::expected_signed_cert_timestamps }, + { "-ocsp-response", &TestConfig::ocsp_response }, }; const Flag kIntFlags[] = { diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h index 29a1c773..4418ed37 100644 --- a/ssl/test/test_config.h +++ b/ssl/test/test_config.h @@ -86,6 +86,7 @@ struct TestConfig { 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 164d6a53..69994bcc 100644 --- a/tool/server.cc +++ b/tool/server.cc @@ -34,11 +34,53 @@ static const struct argument kArguments[] = { "-key", kOptionalArgument, "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 &args) { if (!InitSocketLibrary()) { return false; @@ -74,6 +116,12 @@ bool Server(const std::vector &args) { 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;