Skip to content

Commit 371b5bb

Browse files
committed
tls: preserve servername on resumed sessions
1 parent 6964b53 commit 371b5bb

File tree

5 files changed

+126
-4
lines changed

5 files changed

+126
-4
lines changed

deps/ncrypto/ncrypto.cc

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2941,8 +2941,25 @@ std::optional<const std::string_view> SSLPointer::GetServerName(
29412941
const SSL* ssl) {
29422942
if (ssl == nullptr) return std::nullopt;
29432943
auto res = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
2944-
if (res == nullptr) return std::nullopt;
2945-
return res;
2944+
if (res != nullptr) return res;
2945+
2946+
#ifndef OPENSSL_IS_BORINGSSL
2947+
const SSL_SESSION* sess = SSL_get_session(ssl);
2948+
if (sess == nullptr) return std::nullopt;
2949+
2950+
res = SSL_SESSION_get0_hostname(sess);
2951+
if (res != nullptr) return res;
2952+
2953+
void* appdata = nullptr;
2954+
size_t appdata_len = 0;
2955+
if (SSL_SESSION_get0_ticket_appdata(
2956+
const_cast<SSL_SESSION*>(sess), &appdata, &appdata_len) == 1 &&
2957+
appdata != nullptr && appdata_len > 0) {
2958+
return std::string_view(static_cast<const char*>(appdata), appdata_len);
2959+
}
2960+
#endif
2961+
2962+
return std::nullopt;
29462963
}
29472964

29482965
std::optional<const std::string_view> SSLPointer::getServerName() const {

src/crypto/crypto_context.cc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <wincrypt.h>
2828
#endif
2929

30+
#include <cstring>
3031
#include <set>
3132

3233
namespace node {
@@ -1548,6 +1549,38 @@ void SecureContext::SetSelectSNIContextCallback(SelectSNIContextCb cb) {
15481549
SSL_CTX_set_tlsext_servername_callback(ctx_.get(), cb);
15491550
}
15501551

1552+
void SecureContext::RememberSessionServername(const unsigned char* session_id,
1553+
unsigned int session_id_length,
1554+
std::string_view servername) {
1555+
if (session_id == nullptr || session_id_length == 0 || servername.empty())
1556+
return;
1557+
1558+
std::string key(reinterpret_cast<const char*>(session_id),
1559+
session_id_length);
1560+
1561+
if (session_servernames_.size() >= kSessionServernameCacheLimit &&
1562+
session_servernames_.find(key) == session_servernames_.end()) {
1563+
session_servernames_.clear();
1564+
}
1565+
1566+
session_servernames_[std::move(key)] = std::string(servername);
1567+
}
1568+
1569+
std::optional<std::string> SecureContext::GetSessionServername(
1570+
const unsigned char* session_id,
1571+
unsigned int session_id_length) const {
1572+
if (session_id == nullptr || session_id_length == 0)
1573+
return std::nullopt;
1574+
1575+
std::string key(reinterpret_cast<const char*>(session_id),
1576+
session_id_length);
1577+
auto it = session_servernames_.find(key);
1578+
if (it == session_servernames_.end())
1579+
return std::nullopt;
1580+
1581+
return it->second;
1582+
}
1583+
15511584
void SecureContext::SetKeylogCallback(KeylogCb cb) {
15521585
SSL_CTX_set_keylog_callback(ctx_.get(), cb);
15531586
}
@@ -2303,6 +2336,22 @@ int SecureContext::TicketCompatibilityCallback(SSL* ssl,
23032336
SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl)));
23042337

23052338
if (enc) {
2339+
#ifndef OPENSSL_IS_BORINGSSL
2340+
// Persist SNI in the session before serializing/encrypting the ticket so
2341+
// it remains available on resumed handshakes.
2342+
if (const char* servername =
2343+
SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
2344+
servername != nullptr) {
2345+
SSL_SESSION* sess = SSL_get_session(ssl);
2346+
if (sess != nullptr &&
2347+
(SSL_SESSION_set1_hostname(sess, servername) != 1 ||
2348+
SSL_SESSION_set1_ticket_appdata(
2349+
sess, servername, strlen(servername)) != 1)) {
2350+
return -1;
2351+
}
2352+
}
2353+
#endif
2354+
23062355
memcpy(name, sc->ticket_key_name_, sizeof(sc->ticket_key_name_));
23072356
if (!ncrypto::CSPRNG(iv, 16) ||
23082357
EVP_EncryptInit_ex(

src/crypto/crypto_context.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
#include "memory_tracker.h"
1111
#include "v8.h"
1212

13+
#include <optional>
14+
#include <string>
15+
#include <string_view>
16+
#include <unordered_map>
17+
1318
namespace node {
1419
namespace crypto {
1520
// A maxVersion of 0 means "any", but OpenSSL may support TLS versions that
@@ -53,6 +58,12 @@ class SecureContext final : public BaseObject {
5358
void SetKeylogCallback(KeylogCb cb);
5459
void SetNewSessionCallback(NewSessionCb cb);
5560
void SetSelectSNIContextCallback(SelectSNIContextCb cb);
61+
void RememberSessionServername(const unsigned char* session_id,
62+
unsigned int session_id_length,
63+
std::string_view servername);
64+
std::optional<std::string> GetSessionServername(
65+
const unsigned char* session_id,
66+
unsigned int session_id_length) const;
5667

5768
inline const ncrypto::X509Pointer& issuer() const { return issuer_; }
5869
inline const ncrypto::X509Pointer& cert() const { return cert_; }
@@ -144,6 +155,8 @@ class SecureContext final : public BaseObject {
144155
void Reset();
145156

146157
private:
158+
static constexpr size_t kSessionServernameCacheLimit = 4096;
159+
147160
ncrypto::SSLCtxPointer ctx_;
148161
ncrypto::X509Pointer cert_;
149162
ncrypto::X509Pointer issuer_;
@@ -157,6 +170,7 @@ class SecureContext final : public BaseObject {
157170
unsigned char ticket_key_name_[16];
158171
unsigned char ticket_key_aes_[16];
159172
unsigned char ticket_key_hmac_[16];
173+
std::unordered_map<std::string, std::string> session_servernames_;
160174
};
161175

162176
int SSL_CTX_use_certificate_chain(SSL_CTX* ctx,

src/crypto/crypto_tls.cc

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include "crypto/crypto_tls.h"
2323
#include <cstdio>
24+
#include <cstring>
2425
#include "async_wrap-inl.h"
2526
#include "crypto/crypto_bio.h"
2627
#include "crypto/crypto_clienthello-inl.h"
@@ -163,6 +164,29 @@ int NewSessionCallback(SSL* s, SSL_SESSION* sess) {
163164
HandleScope handle_scope(env->isolate());
164165
Context::Scope context_scope(env->context());
165166

167+
#ifndef OPENSSL_IS_BORINGSSL
168+
// OpenSSL does not expose the SNI via SSL_get_servername() on resumed
169+
// handshakes. Persist the original SNI in the session so it can be restored
170+
// when the handshake is resumed.
171+
if (const char* servername =
172+
SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
173+
servername != nullptr) {
174+
if (SSL_SESSION_set1_hostname(sess, servername) != 1 ||
175+
SSL_SESSION_set1_ticket_appdata(sess, servername, strlen(servername)) !=
176+
1) {
177+
return 0;
178+
}
179+
180+
unsigned int session_id_length = 0;
181+
const unsigned char* session_id_data =
182+
SSL_SESSION_get_id(sess, &session_id_length);
183+
if (SecureContext* sc = w->secure_context(); sc != nullptr) {
184+
sc->RememberSessionServername(
185+
session_id_data, session_id_length, servername);
186+
}
187+
}
188+
#endif
189+
166190
if (!w->has_session_callbacks()) [[unlikely]]
167191
return 0;
168192

@@ -1367,9 +1391,26 @@ void TLSWrap::GetServername(const FunctionCallbackInfo<Value>& args) {
13671391
auto& sn = servername.value();
13681392
args.GetReturnValue().Set(
13691393
OneByteString(args.GetIsolate(), sn.data(), sn.length()));
1370-
} else {
1371-
args.GetReturnValue().Set(false);
1394+
return;
13721395
}
1396+
1397+
SSL_SESSION* sess = SSL_get_session(wrap->ssl_.get());
1398+
if (sess != nullptr && wrap->secure_context() != nullptr) {
1399+
unsigned int session_id_length = 0;
1400+
const unsigned char* session_id_data =
1401+
SSL_SESSION_get_id(sess, &session_id_length);
1402+
1403+
auto cached = wrap->secure_context()->GetSessionServername(
1404+
session_id_data, session_id_length);
1405+
if (cached.has_value()) {
1406+
auto& sn = cached.value();
1407+
args.GetReturnValue().Set(
1408+
OneByteString(args.GetIsolate(), sn.data(), sn.length()));
1409+
return;
1410+
}
1411+
}
1412+
1413+
args.GetReturnValue().Set(false);
13731414
}
13741415

13751416
void TLSWrap::SetServername(const FunctionCallbackInfo<Value>& args) {

src/crypto/crypto_tls.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class TLSWrap : public AsyncWrap,
6969
inline bool is_server() const { return kind_ == Kind::kServer; }
7070
inline bool is_client() const { return kind_ == Kind::kClient; }
7171
inline bool is_awaiting_new_session() const { return awaiting_new_session_; }
72+
inline SecureContext* secure_context() const { return sc_.get(); }
7273

7374
// Implement StreamBase:
7475
bool IsAlive() override;

0 commit comments

Comments
 (0)