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