Add SSL_SESSION_copy_without_early_data.
While we could store an extra boolean along with each session in the
session cache and then disable early data on a per-socket level, that
causes SSL_early_data_reason to report confusing values. Clearing it
at the session seems simpler. Since sessions are intended to be
mutable, do this as a copy operation.
Bug: chromium:1066623
Change-Id: I599b1559b696805e330ab5c2d61e4158440daef8
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/40464
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 66243e2..b9ed71e 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -3365,6 +3365,18 @@
// attempted with |session| if enabled.
OPENSSL_EXPORT int SSL_SESSION_early_data_capable(const SSL_SESSION *session);
+// SSL_SESSION_copy_without_early_data returns a copy of |session| with early
+// data disabled. If |session| already does not support early data, it returns
+// |session| with the reference count increased. The caller takes ownership of
+// the result and must release it with |SSL_SESSION_free|.
+//
+// This function may be used on the client to clear early data support from
+// existing sessions when the server rejects early data. In particular,
+// |SSL_R_WRONG_VERSION_ON_EARLY_DATA| requires a fresh connection to retry, and
+// the client would not want 0-RTT enabled for the next connection attempt.
+OPENSSL_EXPORT SSL_SESSION *SSL_SESSION_copy_without_early_data(
+ SSL_SESSION *session);
+
// SSL_early_data_accepted returns whether early data was accepted on the
// handshake performed by |ssl|.
OPENSSL_EXPORT int SSL_early_data_accepted(const SSL *ssl);
diff --git a/ssl/ssl_session.cc b/ssl/ssl_session.cc
index 8819239..0d372cb 100644
--- a/ssl/ssl_session.cc
+++ b/ssl/ssl_session.cc
@@ -1060,6 +1060,24 @@
session->ticket_max_early_data != 0;
}
+SSL_SESSION *SSL_SESSION_copy_without_early_data(SSL_SESSION *session) {
+ if (!SSL_SESSION_early_data_capable(session)) {
+ return UpRef(session).release();
+ }
+
+ bssl::UniquePtr<SSL_SESSION> copy =
+ SSL_SESSION_dup(session, SSL_SESSION_DUP_ALL);
+ if (!copy) {
+ return nullptr;
+ }
+
+ copy->ticket_max_early_data = 0;
+ // Copied sessions are non-resumable until they're completely filled in.
+ copy->not_resumable = session->not_resumable;
+ assert(!SSL_SESSION_early_data_capable(copy.get()));
+ return copy.release();
+}
+
SSL_SESSION *SSL_magic_pending_session_ptr(void) {
return (SSL_SESSION *)&g_pending_session_magic;
}
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index 96d67bc..2aaff76 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -6145,5 +6145,59 @@
EXPECT_EQ(SSL_R_NO_RENEGOTIATION, ERR_GET_REASON(err));
}
+
+TEST(SSLTest, CopyWithoutEarlyData) {
+ bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
+ bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method()));
+ ASSERT_TRUE(client_ctx);
+ ASSERT_TRUE(server_ctx);
+
+ bssl::UniquePtr<X509> cert = GetTestCertificate();
+ bssl::UniquePtr<EVP_PKEY> key = GetTestKey();
+ ASSERT_TRUE(cert);
+ ASSERT_TRUE(key);
+ ASSERT_TRUE(SSL_CTX_use_certificate(server_ctx.get(), cert.get()));
+ ASSERT_TRUE(SSL_CTX_use_PrivateKey(server_ctx.get(), key.get()));
+
+ SSL_CTX_set_session_cache_mode(client_ctx.get(), SSL_SESS_CACHE_BOTH);
+ SSL_CTX_set_session_cache_mode(server_ctx.get(), SSL_SESS_CACHE_BOTH);
+ SSL_CTX_set_early_data_enabled(client_ctx.get(), 1);
+ SSL_CTX_set_early_data_enabled(server_ctx.get(), 1);
+
+ bssl::UniquePtr<SSL_SESSION> session =
+ CreateClientSession(client_ctx.get(), server_ctx.get());
+ ASSERT_TRUE(session);
+
+ // The client should attempt early data with |session|.
+ auto config = ClientConfig();
+ config.early_data = true;
+ config.session = session.get();
+ bssl::UniquePtr<SSL> client, server;
+ ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
+ server_ctx.get(), config,
+ /*do_handshake=*/false));
+ ASSERT_EQ(1, SSL_do_handshake(client.get()));
+ EXPECT_TRUE(SSL_in_early_data(client.get()));
+
+ // |SSL_SESSION_copy_without_early_data| should disable early data but
+ // still resume the session.
+ bssl::UniquePtr<SSL_SESSION> session2(
+ SSL_SESSION_copy_without_early_data(session.get()));
+ ASSERT_TRUE(session2);
+ EXPECT_NE(session.get(), session2.get());
+ config.session = session2.get();
+ ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
+ server_ctx.get(), config));
+ EXPECT_TRUE(SSL_session_reused(client.get()));
+ EXPECT_EQ(ssl_early_data_unsupported_for_session,
+ SSL_get_early_data_reason(client.get()));
+
+ // |SSL_SESSION_copy_without_early_data| should be a reference count increase
+ // when passed an early-data-incapable session.
+ bssl::UniquePtr<SSL_SESSION> session3(
+ SSL_SESSION_copy_without_early_data(session2.get()));
+ EXPECT_EQ(session2.get(), session3.get());
+}
+
} // namespace
BSSL_NAMESPACE_END