Make SNI per-connection, not per-session.
Right now we report the per-connection value during the handshake and
the per-session value after the handshake. This also trims our tickets
slightly by removing a largely unused field from SSL_SESSION.
Putting it on SSL_HANDSHAKE would be better, but sadly a number of
bindings-type APIs expose it after the handshake.
Change-Id: I6a1383f95da9b1b141b9d6adadc05ee1e458a326
Reviewed-on: https://boringssl-review.googlesource.com/20064
Commit-Queue: David Benjamin <davidben@google.com>
Commit-Queue: Steven Valdez <svaldez@google.com>
Reviewed-by: Steven Valdez <svaldez@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 269bc8b..e917fb4 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2512,8 +2512,7 @@
// SSL_get_servername, for a server, returns the hostname supplied by the
// client or NULL if there was none. The |type| argument must be
-// |TLSEXT_NAMETYPE_host_name|. Note that the returned pointer points inside
-// |ssl| and is only valid until the next operation on |ssl|.
+// |TLSEXT_NAMETYPE_host_name|.
OPENSSL_EXPORT const char *SSL_get_servername(const SSL *ssl, const int type);
// SSL_get_servername_type, for a server, returns |TLSEXT_NAMETYPE_host_name|
@@ -4070,7 +4069,6 @@
// These are used to make removal of session-ids more efficient and to
// implement a maximum cache size.
SSL_SESSION *prev, *next;
- char *tlsext_hostname;
// RFC4507 info
uint8_t *tlsext_tick; // Session ticket
diff --git a/ssl/handshake_server.cc b/ssl/handshake_server.cc
index f196db0..10e618d 100644
--- a/ssl/handshake_server.cc
+++ b/ssl/handshake_server.cc
@@ -632,16 +632,6 @@
if (ssl->session == NULL) {
hs->new_session->cipher = hs->new_cipher;
- // On new sessions, stash the SNI value in the session.
- if (hs->hostname != NULL) {
- OPENSSL_free(hs->new_session->tlsext_hostname);
- hs->new_session->tlsext_hostname = BUF_strdup(hs->hostname.get());
- if (hs->new_session->tlsext_hostname == NULL) {
- ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
- return ssl_hs_error;
- }
- }
-
// Determine whether to request a client certificate.
hs->cert_request = !!(ssl->verify_mode & SSL_VERIFY_PEER);
// Only request a certificate if Channel ID isn't negotiated.
diff --git a/ssl/internal.h b/ssl/internal.h
index 19968ac..0e44de8 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1208,9 +1208,6 @@
uint8_t *certificate_types = nullptr;
size_t num_certificate_types = 0;
- // hostname, on the server, is the value of the SNI extension.
- UniquePtr<char> hostname;
-
// local_pubkey is the public key we are authenticating as.
UniquePtr<EVP_PKEY> local_pubkey;
@@ -1764,6 +1761,9 @@
uint8_t *alpn_selected;
size_t alpn_selected_len;
+ // hostname, on the server, is the value of the SNI extension.
+ char *hostname;
+
// For a server:
// If |tlsext_channel_id_valid| is true, then this contains the
// verified Channel ID from the client: a P256 point, (x,y), where
diff --git a/ssl/s3_lib.cc b/ssl/s3_lib.cc
index fb705aa..dcf9559 100644
--- a/ssl/s3_lib.cc
+++ b/ssl/s3_lib.cc
@@ -208,6 +208,7 @@
ssl_handshake_free(ssl->s3->hs);
OPENSSL_free(ssl->s3->next_proto_negotiated);
OPENSSL_free(ssl->s3->alpn_selected);
+ OPENSSL_free(ssl->s3->hostname);
Delete(ssl->s3->aead_read_ctx);
Delete(ssl->s3->aead_write_ctx);
BUF_MEM_free(ssl->s3->pending_flight);
diff --git a/ssl/ssl_asn1.cc b/ssl/ssl_asn1.cc
index 73908db..7bcfdd7 100644
--- a/ssl/ssl_asn1.cc
+++ b/ssl/ssl_asn1.cc
@@ -119,8 +119,6 @@
// peer [3] Certificate OPTIONAL,
// sessionIDContext [4] OCTET STRING OPTIONAL,
// verifyResult [5] INTEGER OPTIONAL, -- one of X509_V_* codes
-// hostName [6] OCTET STRING OPTIONAL,
-// -- from server_name extension
// pskIdentity [8] OCTET STRING OPTIONAL,
// ticketLifetimeHint [9] INTEGER OPTIONAL, -- client-only
// ticket [10] OCTET STRING OPTIONAL, -- client-only
@@ -142,9 +140,11 @@
// }
//
// Note: historically this serialization has included other optional
-// fields. Their presence is currently treated as a parse error:
+// fields. Their presence is currently treated as a parse error, except for
+// hostName, which is ignored.
//
// keyArg [0] IMPLICIT OCTET STRING OPTIONAL,
+// hostName [6] OCTET STRING OPTIONAL,
// pskIdentityHint [7] OCTET STRING OPTIONAL,
// compressionMethod [11] OCTET STRING OPTIONAL,
// srpUsername [12] OCTET STRING OPTIONAL,
@@ -254,16 +254,6 @@
}
}
- if (in->tlsext_hostname) {
- if (!CBB_add_asn1(&session, &child, kHostNameTag) ||
- !CBB_add_asn1(&child, &child2, CBS_ASN1_OCTETSTRING) ||
- !CBB_add_bytes(&child2, (const uint8_t *)in->tlsext_hostname,
- strlen(in->tlsext_hostname))) {
- OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
- return 0;
- }
- }
-
if (in->psk_identity) {
if (!CBB_add_asn1(&session, &child, kPSKIdentityTag) ||
!CBB_add_asn1(&child, &child2, CBS_ASN1_OCTETSTRING) ||
@@ -627,10 +617,19 @@
&session, ret->sid_ctx, &ret->sid_ctx_length, sizeof(ret->sid_ctx),
kSessionIDContextTag) ||
!SSL_SESSION_parse_long(&session, &ret->verify_result, kVerifyResultTag,
- X509_V_OK) ||
- !SSL_SESSION_parse_string(&session, &ret->tlsext_hostname,
- kHostNameTag) ||
- !SSL_SESSION_parse_string(&session, &ret->psk_identity,
+ X509_V_OK)) {
+ return nullptr;
+ }
+
+ // Skip the historical hostName field.
+ CBS unused_hostname;
+ if (!CBS_get_optional_asn1(&session, &unused_hostname, nullptr,
+ kHostNameTag)) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
+ return nullptr;
+ }
+
+ if (!SSL_SESSION_parse_string(&session, &ret->psk_identity,
kPSKIdentityTag) ||
!SSL_SESSION_parse_u32(&session, &ret->tlsext_tick_lifetime_hint,
kTicketLifetimeHintTag, 0) ||
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 853994b..7a75776 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -1755,25 +1755,11 @@
return ssl->tlsext_hostname;
}
- // During the handshake, report the handshake value.
- if (ssl->s3->hs != NULL) {
- return ssl->s3->hs->hostname.get();
- }
-
- // SSL_get_servername may also be called after the handshake to look up the
- // SNI value.
- //
- // TODO(davidben): This is almost unused. Can we remove it?
- SSL_SESSION *session = SSL_get_session(ssl);
- if (session == NULL) {
- return NULL;
- }
- return session->tlsext_hostname;
+ return ssl->s3->hostname;
}
int SSL_get_servername_type(const SSL *ssl) {
- SSL_SESSION *session = SSL_get_session(ssl);
- if (session == NULL || session->tlsext_hostname == NULL) {
+ if (SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name) == NULL) {
return -1;
}
return TLSEXT_NAMETYPE_host_name;
diff --git a/ssl/ssl_session.cc b/ssl/ssl_session.cc
index 32d3b35..c21c282 100644
--- a/ssl/ssl_session.cc
+++ b/ssl/ssl_session.cc
@@ -242,13 +242,6 @@
SHA256_DIGEST_LENGTH);
new_session->peer_sha256_valid = session->peer_sha256_valid;
- if (session->tlsext_hostname != NULL) {
- new_session->tlsext_hostname = BUF_strdup(session->tlsext_hostname);
- if (new_session->tlsext_hostname == NULL) {
- return nullptr;
- }
- }
-
new_session->peer_signature_algorithm = session->peer_signature_algorithm;
new_session->timeout = session->timeout;
@@ -889,7 +882,6 @@
OPENSSL_cleanse(session->session_id, sizeof(session->session_id));
sk_CRYPTO_BUFFER_pop_free(session->certs, CRYPTO_BUFFER_free);
session->x509_method->session_clear(session);
- OPENSSL_free(session->tlsext_hostname);
OPENSSL_free(session->tlsext_tick);
CRYPTO_BUFFER_free(session->signed_cert_timestamp_list);
CRYPTO_BUFFER_free(session->ocsp_response);
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index dd795a6..48e50ee 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -542,14 +542,14 @@
// filling in missing fields from |kOpenSSLSession|. This includes
// providing |peer_sha256|, so |peer| is not serialized.
static const char kCustomSession[] =
- "MIIBdgIBAQICAwMEAsAvBCAG5Q1ndq4Yfmbeo1zwLkNRKmCXGdNgWvGT3cskV0yQ"
+ "MIIBZAIBAQICAwMEAsAvBCAG5Q1ndq4Yfmbeo1zwLkNRKmCXGdNgWvGT3cskV0yQ"
"kAQwJlrlzkAWBOWiLj/jJ76D7l+UXoizP2KI2C7I2FccqMmIfFmmkUy32nIJ0mZH"
- "IWoJoQYCBFRDO46iBAICASykAwQBAqUDAgEUphAEDnd3dy5nb29nbGUuY29tqAcE"
- "BXdvcmxkqQUCAwGJwKqBpwSBpBwUQvoeOk0Kg36SYTcLEkXqKwOBfF9vE4KX0Nxe"
- "LwjcDTpsuh3qXEaZ992r1N38VDcyS6P7I6HBYN9BsNHM362zZnY27GpTw+Kwd751"
- "CLoXFPoaMOe57dbBpXoro6Pd3BTbf/Tzr88K06yEOTDKPNj3+inbMaVigtK4PLyP"
- "q+Topyzvx9USFgRvyuoxn0Hgb+R0A3j6SLRuyOdAi4gv7Y5oliynrSIEIAYGBgYG"
- "BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGrgMEAQevAwQBBLADBAEF";
+ "IWoJoQYCBFRDO46iBAICASykAwQBAqUDAgEUqAcEBXdvcmxkqQUCAwGJwKqBpwSB"
+ "pBwUQvoeOk0Kg36SYTcLEkXqKwOBfF9vE4KX0NxeLwjcDTpsuh3qXEaZ992r1N38"
+ "VDcyS6P7I6HBYN9BsNHM362zZnY27GpTw+Kwd751CLoXFPoaMOe57dbBpXoro6Pd"
+ "3BTbf/Tzr88K06yEOTDKPNj3+inbMaVigtK4PLyPq+Topyzvx9USFgRvyuoxn0Hg"
+ "b+R0A3j6SLRuyOdAi4gv7Y5oliynrSIEIAYGBgYGBgYGBgYGBgYGBgYGBgYGBgYG"
+ "BgYGBgYGBgYGrgMEAQevAwQBBLADBAEF";
// kBoringSSLSession is a serialized SSL_SESSION generated from bssl client.
static const char kBoringSSLSession[] =
@@ -1508,10 +1508,15 @@
return true;
}
+struct ClientConfig {
+ SSL_SESSION *session = nullptr;
+ std::string servername;
+};
+
static bool ConnectClientAndServer(bssl::UniquePtr<SSL> *out_client,
bssl::UniquePtr<SSL> *out_server,
SSL_CTX *client_ctx, SSL_CTX *server_ctx,
- SSL_SESSION *session) {
+ const ClientConfig &config = ClientConfig()) {
bssl::UniquePtr<SSL> client(SSL_new(client_ctx)), server(SSL_new(server_ctx));
if (!client || !server) {
return false;
@@ -1519,7 +1524,13 @@
SSL_set_connect_state(client.get());
SSL_set_accept_state(server.get());
- SSL_set_session(client.get(), session);
+ if (config.session) {
+ SSL_set_session(client.get(), config.session);
+ }
+ if (!config.servername.empty() &&
+ !SSL_set_tlsext_host_name(client.get(), config.servername.c_str())) {
+ return false;
+ }
BIO *bio1, *bio2;
if (!BIO_new_bio_pair(&bio1, 0, &bio2, 0)) {
@@ -1573,9 +1584,9 @@
SSL_CTX_use_PrivateKey(ctx, key_.get());
}
- bool Connect() {
+ bool Connect(const ClientConfig &config = ClientConfig()) {
return ConnectClientAndServer(&client_, &server_, client_ctx_.get(),
- server_ctx_.get(), nullptr /* no session */);
+ server_ctx_.get(), config);
}
uint16_t version() const { return GetParam().version; }
@@ -1679,8 +1690,7 @@
bssl::UniquePtr<SSL> client, server;
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
- server_ctx.get(),
- nullptr /* no session */));
+ server_ctx.get()));
SSL_SESSION *session0 = SSL_get_session(client.get());
bssl::UniquePtr<SSL_SESSION> session1 =
@@ -2117,15 +2127,16 @@
return 1;
}
-static bssl::UniquePtr<SSL_SESSION> CreateClientSession(SSL_CTX *client_ctx,
- SSL_CTX *server_ctx) {
+static bssl::UniquePtr<SSL_SESSION> CreateClientSession(
+ SSL_CTX *client_ctx, SSL_CTX *server_ctx,
+ const ClientConfig &config = ClientConfig()) {
g_last_session = nullptr;
SSL_CTX_sess_set_new_cb(client_ctx, SaveLastSession);
// Connect client and server to get a session.
bssl::UniquePtr<SSL> client, server;
if (!ConnectClientAndServer(&client, &server, client_ctx, server_ctx,
- nullptr /* no session */)) {
+ config)) {
fprintf(stderr, "Failed to connect client and server.\n");
return nullptr;
}
@@ -2145,8 +2156,10 @@
static void ExpectSessionReused(SSL_CTX *client_ctx, SSL_CTX *server_ctx,
SSL_SESSION *session, bool want_reused) {
bssl::UniquePtr<SSL> client, server;
- EXPECT_TRUE(ConnectClientAndServer(&client, &server, client_ctx, server_ctx,
- session));
+ ClientConfig config;
+ config.session = session;
+ EXPECT_TRUE(
+ ConnectClientAndServer(&client, &server, client_ctx, server_ctx, config));
EXPECT_EQ(SSL_session_reused(client.get()), SSL_session_reused(server.get()));
@@ -2161,8 +2174,10 @@
SSL_CTX_sess_set_new_cb(client_ctx, SaveLastSession);
bssl::UniquePtr<SSL> client, server;
- if (!ConnectClientAndServer(&client, &server, client_ctx,
- server_ctx, session)) {
+ ClientConfig config;
+ config.session = session;
+ if (!ConnectClientAndServer(&client, &server, client_ctx, server_ctx,
+ config)) {
fprintf(stderr, "Failed to connect client and server.\n");
return nullptr;
}
@@ -2640,7 +2655,7 @@
bssl::UniquePtr<SSL> client, server;
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
- server_ctx.get(), nullptr));
+ server_ctx.get()));
EXPECT_EQ(TLS1_2_VERSION, SSL_version(client.get()));
}
@@ -3047,6 +3062,46 @@
}
}
+TEST_P(SSLVersionTest, GetServerName) {
+ // No extensions in SSL 3.0.
+ if (version() == SSL3_VERSION) {
+ return;
+ }
+
+ ClientConfig config;
+ config.servername = "host1";
+
+ SSL_CTX_set_tlsext_servername_callback(
+ server_ctx_.get(), [](SSL *ssl, int *out_alert, void *arg) -> int {
+ // During the handshake, |SSL_get_servername| must match |config|.
+ ClientConfig *config_p = reinterpret_cast<ClientConfig *>(arg);
+ EXPECT_STREQ(config_p->servername.c_str(),
+ SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name));
+ return SSL_TLSEXT_ERR_OK;
+ });
+ SSL_CTX_set_tlsext_servername_arg(server_ctx_.get(), &config);
+
+ ASSERT_TRUE(Connect(config));
+ // After the handshake, it must also be available.
+ EXPECT_STREQ(config.servername.c_str(),
+ SSL_get_servername(server_.get(), TLSEXT_NAMETYPE_host_name));
+
+ // Establish a session under host1.
+ 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);
+ bssl::UniquePtr<SSL_SESSION> session =
+ CreateClientSession(client_ctx_.get(), server_ctx_.get(), config);
+
+ // If the client resumes a session with a different name, |SSL_get_servername|
+ // must return the new name.
+ ASSERT_TRUE(session);
+ config.session = session.get();
+ config.servername = "host2";
+ ASSERT_TRUE(Connect(config));
+ EXPECT_STREQ(config.servername.c_str(),
+ SSL_get_servername(server_.get(), TLSEXT_NAMETYPE_host_name));
+}
+
TEST(SSLTest, AddChainCertHack) {
// Ensure that we don't accidently break the hack that we have in place to
// keep curl and serf happy when they use an |X509| even after transfering
@@ -3145,8 +3200,7 @@
bssl::UniquePtr<SSL> client, server;
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
- server_ctx.get(),
- nullptr /* no session */));
+ server_ctx.get()));
}
TEST(SSLTest, ClientCABuffers) {
@@ -3208,8 +3262,7 @@
bssl::UniquePtr<SSL> client, server;
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
- server_ctx.get(),
- nullptr /* no session */));
+ server_ctx.get()));
EXPECT_TRUE(cert_cb_called);
}
@@ -3487,16 +3540,14 @@
// way to enable SSL 3.0.
bssl::UniquePtr<SSL> client, server;
EXPECT_FALSE(ConnectClientAndServer(&client, &server, tls_ctx.get(),
- ssl3_ctx.get(),
- nullptr /* no session */));
+ ssl3_ctx.get()));
uint32_t err = ERR_get_error();
EXPECT_EQ(ERR_LIB_SSL, ERR_GET_LIB(err));
EXPECT_EQ(SSL_R_NO_SUPPORTED_VERSIONS_ENABLED, ERR_GET_REASON(err));
// Likewise for SSLv3_method clients.
EXPECT_FALSE(ConnectClientAndServer(&client, &server, ssl3_ctx.get(),
- tls_ctx.get(),
- nullptr /* no session */));
+ tls_ctx.get()));
err = ERR_get_error();
EXPECT_EQ(ERR_LIB_SSL, ERR_GET_LIB(err));
EXPECT_EQ(SSL_R_NO_SUPPORTED_VERSIONS_ENABLED, ERR_GET_REASON(err));
@@ -3560,8 +3611,7 @@
bssl::UniquePtr<SSL> client, server;
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
- server_ctx.get(),
- nullptr /* no session */));
+ server_ctx.get()));
const std::vector<uint8_t> record = {1, 2, 3, 4, 5};
std::vector<uint8_t> prefix(
@@ -3604,8 +3654,7 @@
bssl::UniquePtr<SSL> client, server;
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
- server_ctx.get(),
- nullptr /* no session */));
+ server_ctx.get()));
const std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
std::vector<uint8_t> record = plaintext;
@@ -3643,8 +3692,7 @@
bssl::UniquePtr<SSL> client, server;
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
- server_ctx.get(),
- nullptr /* no session */));
+ server_ctx.get()));
const std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
std::vector<uint8_t> record = plaintext;
@@ -3683,8 +3731,7 @@
bssl::UniquePtr<SSL> client, server;
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
- server_ctx.get(),
- nullptr /* no session */));
+ server_ctx.get()));
std::vector<uint8_t> record = {1, 2, 3, 4, 5};
std::vector<uint8_t> prefix(
diff --git a/ssl/t1_lib.cc b/ssl/t1_lib.cc
index 481c9f8..ec70d27 100644
--- a/ssl/t1_lib.cc
+++ b/ssl/t1_lib.cc
@@ -619,31 +619,14 @@
static int ext_sni_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
CBS *contents) {
- SSL *const ssl = hs->ssl;
- if (contents == NULL) {
- return 1;
- }
-
- if (CBS_len(contents) != 0) {
- return 0;
- }
-
- assert(ssl->tlsext_hostname != NULL);
-
- if (ssl->session == NULL) {
- OPENSSL_free(hs->new_session->tlsext_hostname);
- hs->new_session->tlsext_hostname = BUF_strdup(ssl->tlsext_hostname);
- if (!hs->new_session->tlsext_hostname) {
- *out_alert = SSL_AD_INTERNAL_ERROR;
- return 0;
- }
- }
-
- return 1;
+ // The server may acknowledge SNI with an empty extension. We check the syntax
+ // but otherwise ignore this signal.
+ return contents == NULL || CBS_len(contents) == 0;
}
static int ext_sni_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
CBS *contents) {
+ SSL *const ssl = hs->ssl;
if (contents == NULL) {
return 1;
}
@@ -674,12 +657,10 @@
}
// Copy the hostname as a string.
- char *hostname_raw = nullptr;
- if (!CBS_strdup(&host_name, &hostname_raw)) {
+ if (!CBS_strdup(&host_name, &ssl->s3->hostname)) {
*out_alert = SSL_AD_INTERNAL_ERROR;
return 0;
}
- hs->hostname.reset(hostname_raw);
hs->should_ack_sni = true;
return 1;
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index 65c2f11..cd6baa4 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -413,15 +413,6 @@
// Record connection properties in the new session.
hs->new_session->cipher = hs->new_cipher;
- if (hs->hostname != NULL) {
- OPENSSL_free(hs->new_session->tlsext_hostname);
- hs->new_session->tlsext_hostname = BUF_strdup(hs->hostname.get());
- if (hs->new_session->tlsext_hostname == NULL) {
- ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
- return ssl_hs_error;
- }
- }
-
// Store the initial negotiated ALPN in the session.
if (ssl->s3->alpn_selected != NULL) {
hs->new_session->early_alpn = (uint8_t *)BUF_memdup(