Option to reverify certs on resumption.
Works in the 1.3 and 1.2 client handshakes, not implemented on the
server for now.
Creates an SSL_CTX option to reverify the server certificate on session
resumption. Reverification only runs the client's certificate verify callback.
Adds new states to the client handshakes: state_reverify_server_certificate in
TLS 1.2, and state_server_certificate_reverify in TLS 1.3.
Adds a negative test to make sure that by default we don't verify the
certificate on resumption, and positive tests that make sure we do when the
new option is set.
Change-Id: I3a47ff3eacb3099df4db4c5bc57f7c801ceea8f1
Bug: chromium:347402
Reviewed-on: https://boringssl-review.googlesource.com/29984
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index a24af46..d616981 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -3521,6 +3521,13 @@
OPENSSL_EXPORT void SSL_CTX_set_dos_protection_cb(
SSL_CTX *ctx, int (*cb)(const SSL_CLIENT_HELLO *));
+// SSL_CTX_set_reverify_on_resume configures whether the certificate
+// verification callback will be used to reverify stored certificates
+// when resuming a session. This only works with |SSL_CTX_set_custom_verify|.
+// For now, this is incompatible with |SSL_VERIFY_NONE| mode, and is only
+// respected on clients.
+OPENSSL_EXPORT void SSL_CTX_set_reverify_on_resume(SSL_CTX *ctx, int enabled);
+
// SSL_ST_* are possible values for |SSL_state|, the bitmasks that make them up,
// and some historical values for compatibility. Only |SSL_ST_INIT| and
// |SSL_ST_OK| are ever returned.
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
index 4683ac5..8e5c62c 100644
--- a/ssl/handshake.cc
+++ b/ssl/handshake.cc
@@ -157,8 +157,7 @@
UniquePtr<SSL_HANDSHAKE> ssl_handshake_new(SSL *ssl) {
UniquePtr<SSL_HANDSHAKE> hs = MakeUnique<SSL_HANDSHAKE>(ssl);
- if (!hs ||
- !hs->transcript.Init()) {
+ if (!hs || !hs->transcript.Init()) {
return nullptr;
}
hs->config = ssl->config.get();
@@ -373,6 +372,32 @@
return ret;
}
+// Verifies a stored certificate when resuming a session. A few things are
+// different from verify_peer_cert:
+// 1. We can't be renegotiating if we're resuming a session.
+// 2. The session is immutable, so we don't support verify_mode ==
+// SSL_VERIFY_NONE
+// 3. We don't call the OCSP callback.
+// 4. We only support custom verify callbacks.
+enum ssl_verify_result_t ssl_reverify_peer_cert(SSL_HANDSHAKE *hs) {
+ SSL *const ssl = hs->ssl;
+ assert(ssl->s3->established_session == nullptr);
+ assert(hs->config->verify_mode != SSL_VERIFY_NONE);
+
+ uint8_t alert = SSL_AD_CERTIFICATE_UNKNOWN;
+ enum ssl_verify_result_t ret = ssl_verify_invalid;
+ if (hs->config->custom_verify_callback != nullptr) {
+ ret = hs->config->custom_verify_callback(ssl, &alert);
+ }
+
+ if (ret == ssl_verify_invalid) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_CERTIFICATE_VERIFY_FAILED);
+ ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
+ }
+
+ return ret;
+}
+
uint16_t ssl_get_grease_value(SSL_HANDSHAKE *hs,
enum ssl_grease_index_t index) {
// Draw entropy for all GREASE values at once. This avoids calling
@@ -452,8 +477,7 @@
}
// Log the master secret, if logging is enabled.
- if (!ssl_log_secret(ssl, "CLIENT_RANDOM",
- session->master_key,
+ if (!ssl_log_secret(ssl, "CLIENT_RANDOM", session->master_key,
session->master_key_length)) {
return 0;
}
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc
index de5d8e9..cb9b6de 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -182,6 +182,7 @@
state_read_server_certificate,
state_read_certificate_status,
state_verify_server_certificate,
+ state_reverify_server_certificate,
state_read_server_key_exchange,
state_read_certificate_request,
state_read_server_hello_done,
@@ -734,7 +735,12 @@
ssl->method->next_message(ssl);
if (ssl->session != NULL) {
- hs->state = state_read_session_ticket;
+ if (ssl->ctx->reverify_on_resume &&
+ ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
+ hs->state = state_reverify_server_certificate;
+ } else {
+ hs->state = state_read_session_ticket;
+ }
return ssl_hs_ok;
}
@@ -868,6 +874,23 @@
return ssl_hs_ok;
}
+static enum ssl_hs_wait_t do_reverify_server_certificate(SSL_HANDSHAKE *hs) {
+ assert(hs->ssl->ctx->reverify_on_resume);
+
+ switch (ssl_reverify_peer_cert(hs)) {
+ case ssl_verify_ok:
+ break;
+ case ssl_verify_invalid:
+ return ssl_hs_error;
+ case ssl_verify_retry:
+ hs->state = state_reverify_server_certificate;
+ return ssl_hs_certificate_verify;
+ }
+
+ hs->state = state_read_session_ticket;
+ return ssl_hs_ok;
+}
+
static enum ssl_hs_wait_t do_read_server_key_exchange(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
SSLMessage msg;
@@ -1671,6 +1694,9 @@
case state_verify_server_certificate:
ret = do_verify_server_certificate(hs);
break;
+ case state_reverify_server_certificate:
+ ret = do_reverify_server_certificate(hs);
+ break;
case state_read_server_key_exchange:
ret = do_read_server_key_exchange(hs);
break;
@@ -1745,6 +1771,8 @@
return "TLS client read_certificate_status";
case state_verify_server_certificate:
return "TLS client verify_server_certificate";
+ case state_reverify_server_certificate:
+ return "TLS client reverify_server_certificate";
case state_read_server_key_exchange:
return "TLS client read_server_key_exchange";
case state_read_certificate_request:
diff --git a/ssl/internal.h b/ssl/internal.h
index cc3e075..46c5248 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1698,6 +1698,9 @@
// ssl_verify_peer_cert verifies the peer certificate for |hs|.
enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs);
+// ssl_reverify_peer_cert verifies the peer certificate for |hs| when resuming a
+// session.
+enum ssl_verify_result_t ssl_reverify_peer_cert(SSL_HANDSHAKE *hs);
enum ssl_hs_wait_t ssl_get_finished(SSL_HANDSHAKE *hs);
bool ssl_send_finished(SSL_HANDSHAKE *hs);
@@ -2899,6 +2902,11 @@
// abort.
int (*dos_protection_cb) (const SSL_CLIENT_HELLO *) = nullptr;
+ // Controls whether to verify certificates when resuming connections. They
+ // were already verified when the connection was first made, so the default is
+ // false. For now, this is only respected on clients, not servers.
+ bool reverify_on_resume = false;
+
// Maximum amount of data to send in one fragment. actual record size can be
// more than this due to padding and MAC overheads.
uint16_t max_send_fragment = SSL3_RT_MAX_PLAIN_LENGTH;
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 36c26cd..d6181f3 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -2545,6 +2545,10 @@
ctx->dos_protection_cb = cb;
}
+void SSL_CTX_set_reverify_on_resume(SSL_CTX *ctx, int enabled) {
+ ctx->reverify_on_resume = !!enabled;
+}
+
void SSL_set_renegotiate_mode(SSL *ssl, enum ssl_renegotiate_mode_t mode) {
ssl->renegotiate_mode = mode;
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index d1748dd..47c4513 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -4967,6 +4967,49 @@
shouldFail: true,
expectedError: ":CERTIFICATE_VERIFY_FAILED:",
})
+ // Tests that although the verify callback fails on resumption, by default we don't call it.
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "CertificateVerificationDoesNotFailOnResume" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Certificates: []Certificate{rsaCertificate},
+ },
+ tls13Variant: vers.tls13Variant,
+ flags: append([]string{"-on-resume-verify-fail"}, flags...),
+ resumeSession: true,
+ })
+ if testType == clientTest && useCustomCallback {
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "CertificateVerificationFailsOnResume" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Certificates: []Certificate{rsaCertificate},
+ },
+ tls13Variant: vers.tls13Variant,
+ flags: append([]string{
+ "-on-resume-verify-fail",
+ "-reverify-on-resume",
+ }, flags...),
+ resumeSession: true,
+ shouldFail: true,
+ expectedError: ":CERTIFICATE_VERIFY_FAILED:",
+ })
+ tests = append(tests, testCase{
+ testType: testType,
+ name: "CertificateVerificationPassesOnResume" + suffix,
+ config: Config{
+ MaxVersion: vers.version,
+ Certificates: []Certificate{rsaCertificate},
+ },
+ tls13Variant: vers.tls13Variant,
+ flags: append([]string{
+ "-reverify-on-resume",
+ }, flags...),
+ resumeSession: true,
+ })
+ }
}
}
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index d92cf72..fee4258 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -145,6 +145,7 @@
&TestConfig::install_cert_compression_algs },
{ "-is-handshaker-supported", &TestConfig::is_handshaker_supported },
{ "-handshaker-resume", &TestConfig::handshaker_resume },
+ { "-reverify-on-resume", &TestConfig::reverify_on_resume },
};
const Flag<std::string> kStringFlags[] = {
@@ -1491,6 +1492,9 @@
if (partial_write) {
SSL_set_mode(ssl.get(), SSL_MODE_ENABLE_PARTIAL_WRITE);
}
+ if (reverify_on_resume) {
+ SSL_CTX_set_reverify_on_resume(ssl_ctx, 1);
+ }
if (no_tls13) {
SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1_3);
}
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 6c9ac3e..7f9f442 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -166,6 +166,7 @@
bool decline_ocsp_callback = false;
bool fail_ocsp_callback = false;
bool install_cert_compression_algs = false;
+ bool reverify_on_resume = false;
bool is_handshaker_supported = false;
bool handshaker_resume = false;
std::string handshaker_path;
diff --git a/ssl/tls13_client.cc b/ssl/tls13_client.cc
index 40281a0..cf20403 100644
--- a/ssl/tls13_client.cc
+++ b/ssl/tls13_client.cc
@@ -40,6 +40,7 @@
state_read_certificate_request,
state_read_server_certificate,
state_read_server_certificate_verify,
+ state_server_certificate_reverify,
state_read_server_finished,
state_send_end_of_early_data,
state_send_client_certificate,
@@ -464,6 +465,10 @@
SSL *const ssl = hs->ssl;
// CertificateRequest may only be sent in non-resumption handshakes.
if (ssl->s3->session_reused) {
+ if (ssl->ctx->reverify_on_resume) {
+ hs->tls13_state = state_server_certificate_reverify;
+ return ssl_hs_ok;
+ }
hs->tls13_state = state_read_server_finished;
return ssl_hs_ok;
}
@@ -585,6 +590,21 @@
return ssl_hs_ok;
}
+static enum ssl_hs_wait_t do_server_certificate_reverify(
+ SSL_HANDSHAKE *hs) {
+ switch (ssl_reverify_peer_cert(hs)) {
+ case ssl_verify_ok:
+ break;
+ case ssl_verify_invalid:
+ return ssl_hs_error;
+ case ssl_verify_retry:
+ hs->tls13_state = state_server_certificate_reverify;
+ return ssl_hs_certificate_verify;
+ }
+ hs->tls13_state = state_read_server_finished;
+ return ssl_hs_ok;
+}
+
static enum ssl_hs_wait_t do_read_server_finished(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
SSLMessage msg;
@@ -754,6 +774,9 @@
case state_read_server_certificate_verify:
ret = do_read_server_certificate_verify(hs);
break;
+ case state_server_certificate_reverify:
+ ret = do_server_certificate_reverify(hs);
+ break;
case state_read_server_finished:
ret = do_read_server_finished(hs);
break;
@@ -804,6 +827,8 @@
return "TLS 1.3 client read_server_certificate";
case state_read_server_certificate_verify:
return "TLS 1.3 client read_server_certificate_verify";
+ case state_server_certificate_reverify:
+ return "TLS 1.3 client server_certificate_reverify";
case state_read_server_finished:
return "TLS 1.3 client read_server_finished";
case state_send_end_of_early_data: