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:
Paul Lietar 2015-08-12 11:47:11 +01:00 committed by Adam Langley
parent 12fe1b25ea
commit aeeff2ceee
12 changed files with 187 additions and 19 deletions

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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,

View File

@ -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)) {

View File

@ -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);

View File

@ -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 */);
}

View File

@ -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;
}

View File

@ -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{

View File

@ -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[] = {

View File

@ -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);

View File

@ -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;