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>
This commit is contained in:
parent
12fe1b25ea
commit
aeeff2ceee
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
|
||||
|
23
ssl/t1_lib.c
23
ssl/t1_lib.c
@ -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. */
|
||||
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 */);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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{
|
||||
|
@ -119,6 +119,7 @@ const Flag<std::string> kBase64Flags[] = {
|
||||
{ "-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[] = {
|
||||
|
@ -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);
|
||||
|
@ -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<std::string> &args) {
|
||||
if (!InitSocketLibrary()) {
|
||||
return false;
|
||||
@ -74,6 +116,12 @@ bool Server(const std::vector<std::string> &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;
|
||||
|
Loading…
Reference in New Issue
Block a user