-
Notifications
You must be signed in to change notification settings - Fork 29.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
quic: add additional quic implementation utilities
* add TokenSecret, StatelessResetToken, RetryToken, and RegularToken * add SessionTicket implementation PR-URL: #47289 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Stephen Belanger <[email protected]>
- Loading branch information
Showing
8 changed files
with
960 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC | ||
|
||
#include "sessionticket.h" | ||
#include <env-inl.h> | ||
#include <memory_tracker-inl.h> | ||
#include <ngtcp2/ngtcp2_crypto.h> | ||
#include <node_buffer.h> | ||
#include <node_errors.h> | ||
|
||
namespace node { | ||
|
||
using v8::ArrayBufferView; | ||
using v8::Just; | ||
using v8::Local; | ||
using v8::Maybe; | ||
using v8::MaybeLocal; | ||
using v8::Nothing; | ||
using v8::Object; | ||
using v8::Value; | ||
using v8::ValueDeserializer; | ||
using v8::ValueSerializer; | ||
|
||
namespace quic { | ||
|
||
namespace { | ||
SessionTicket::AppData::Source* GetAppDataSource(SSL* ssl) { | ||
ngtcp2_crypto_conn_ref* ref = | ||
static_cast<ngtcp2_crypto_conn_ref*>(SSL_get_app_data(ssl)); | ||
if (ref != nullptr && ref->user_data != nullptr) { | ||
return static_cast<SessionTicket::AppData::Source*>(ref->user_data); | ||
} | ||
return nullptr; | ||
} | ||
} // namespace | ||
|
||
SessionTicket::SessionTicket(Store&& ticket, Store&& transport_params) | ||
: ticket_(std::move(ticket)), | ||
transport_params_(std::move(transport_params)) {} | ||
|
||
Maybe<SessionTicket> SessionTicket::FromV8Value(Environment* env, | ||
v8::Local<v8::Value> value) { | ||
if (!value->IsArrayBufferView()) { | ||
THROW_ERR_INVALID_ARG_TYPE(env, "The ticket must be an ArrayBufferView."); | ||
return Nothing<SessionTicket>(); | ||
} | ||
|
||
Store content(value.As<ArrayBufferView>()); | ||
ngtcp2_vec vec = content; | ||
|
||
ValueDeserializer des(env->isolate(), vec.base, vec.len); | ||
|
||
if (des.ReadHeader(env->context()).IsNothing()) { | ||
THROW_ERR_INVALID_ARG_VALUE(env, "The ticket format is invalid."); | ||
return Nothing<SessionTicket>(); | ||
} | ||
|
||
Local<Value> ticket; | ||
Local<Value> transport_params; | ||
|
||
errors::TryCatchScope tryCatch(env); | ||
|
||
if (!des.ReadValue(env->context()).ToLocal(&ticket) || | ||
!des.ReadValue(env->context()).ToLocal(&transport_params) || | ||
!ticket->IsArrayBufferView() || !transport_params->IsArrayBufferView()) { | ||
if (tryCatch.HasCaught()) { | ||
// Any errors thrown we want to catch and supress. The only | ||
// error we want to expose to the user is that the ticket format | ||
// is invalid. | ||
if (!tryCatch.HasTerminated()) { | ||
THROW_ERR_INVALID_ARG_VALUE(env, "The ticket format is invalid."); | ||
tryCatch.ReThrow(); | ||
} | ||
return Nothing<SessionTicket>(); | ||
} | ||
THROW_ERR_INVALID_ARG_VALUE(env, "The ticket format is invalid."); | ||
return Nothing<SessionTicket>(); | ||
} | ||
|
||
return Just(SessionTicket(Store(ticket.As<ArrayBufferView>()), | ||
Store(transport_params.As<ArrayBufferView>()))); | ||
} | ||
|
||
MaybeLocal<Object> SessionTicket::encode(Environment* env) const { | ||
auto context = env->context(); | ||
ValueSerializer ser(env->isolate()); | ||
ser.WriteHeader(); | ||
|
||
if (ser.WriteValue(context, ticket_.ToUint8Array(env)).IsNothing() || | ||
ser.WriteValue(context, transport_params_.ToUint8Array(env)) | ||
.IsNothing()) { | ||
return MaybeLocal<Object>(); | ||
} | ||
|
||
auto result = ser.Release(); | ||
|
||
return Buffer::New(env, reinterpret_cast<char*>(result.first), result.second); | ||
} | ||
|
||
const uv_buf_t SessionTicket::ticket() const { | ||
return ticket_; | ||
} | ||
|
||
const ngtcp2_vec SessionTicket::transport_params() const { | ||
return transport_params_; | ||
} | ||
|
||
void SessionTicket::MemoryInfo(MemoryTracker* tracker) const { | ||
tracker->TrackField("ticket", ticket_); | ||
tracker->TrackField("transport_params", transport_params_); | ||
} | ||
|
||
int SessionTicket::GenerateCallback(SSL* ssl, void* arg) { | ||
SessionTicket::AppData::Collect(ssl); | ||
return 1; | ||
} | ||
|
||
SSL_TICKET_RETURN SessionTicket::DecryptedCallback(SSL* ssl, | ||
SSL_SESSION* session, | ||
const unsigned char* keyname, | ||
size_t keyname_len, | ||
SSL_TICKET_STATUS status, | ||
void* arg) { | ||
switch (status) { | ||
default: | ||
return SSL_TICKET_RETURN_IGNORE; | ||
case SSL_TICKET_EMPTY: | ||
[[fallthrough]]; | ||
case SSL_TICKET_NO_DECRYPT: | ||
return SSL_TICKET_RETURN_IGNORE_RENEW; | ||
case SSL_TICKET_SUCCESS_RENEW: | ||
[[fallthrough]]; | ||
case SSL_TICKET_SUCCESS: | ||
return static_cast<SSL_TICKET_RETURN>( | ||
SessionTicket::AppData::Extract(ssl)); | ||
} | ||
} | ||
|
||
SessionTicket::AppData::AppData(SSL* ssl) : ssl_(ssl) {} | ||
|
||
bool SessionTicket::AppData::Set(const uv_buf_t& data) { | ||
if (set_ || data.base == nullptr || data.len == 0) return false; | ||
set_ = true; | ||
SSL_SESSION_set1_ticket_appdata(SSL_get0_session(ssl_), data.base, data.len); | ||
return set_; | ||
} | ||
|
||
std::optional<const uv_buf_t> SessionTicket::AppData::Get() const { | ||
uv_buf_t buf; | ||
int ret = | ||
SSL_SESSION_get0_ticket_appdata(SSL_get0_session(ssl_), | ||
reinterpret_cast<void**>(&buf.base), | ||
reinterpret_cast<size_t*>(&buf.len)); | ||
if (ret != 1) return std::nullopt; | ||
return buf; | ||
} | ||
|
||
void SessionTicket::AppData::Collect(SSL* ssl) { | ||
auto source = GetAppDataSource(ssl); | ||
if (source != nullptr) { | ||
SessionTicket::AppData app_data(ssl); | ||
source->CollectSessionTicketAppData(&app_data); | ||
} | ||
} | ||
|
||
SessionTicket::AppData::Status SessionTicket::AppData::Extract(SSL* ssl) { | ||
auto source = GetAppDataSource(ssl); | ||
if (source != nullptr) { | ||
SessionTicket::AppData app_data(ssl); | ||
return source->ExtractSessionTicketAppData(app_data); | ||
} | ||
return Status::TICKET_IGNORE; | ||
} | ||
|
||
} // namespace quic | ||
} // namespace node | ||
|
||
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
#pragma once | ||
|
||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS | ||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC | ||
|
||
#include <crypto/crypto_common.h> | ||
#include <env.h> | ||
#include <memory_tracker.h> | ||
#include <uv.h> | ||
#include <v8.h> | ||
#include "data.h" | ||
|
||
namespace node { | ||
namespace quic { | ||
|
||
// A TLS 1.3 Session resumption ticket. Encapsulates both the TLS | ||
// ticket and the encoded QUIC transport parameters. The encoded | ||
// structure should be considered to be opaque for end users. | ||
// In JavaScript, the ticket will be represented as a Buffer | ||
// instance with opaque data. To resume a session, the user code | ||
// would pass that Buffer back into to client connection API. | ||
class SessionTicket final : public MemoryRetainer { | ||
public: | ||
static v8::Maybe<SessionTicket> FromV8Value(Environment* env, | ||
v8::Local<v8::Value> value); | ||
|
||
SessionTicket() = default; | ||
SessionTicket(Store&& ticket, Store&& transport_params); | ||
|
||
const uv_buf_t ticket() const; | ||
|
||
const ngtcp2_vec transport_params() const; | ||
|
||
v8::MaybeLocal<v8::Object> encode(Environment* env) const; | ||
|
||
void MemoryInfo(MemoryTracker* tracker) const override; | ||
SET_MEMORY_INFO_NAME(SessionTicket) | ||
SET_SELF_SIZE(SessionTicket) | ||
|
||
class AppData; | ||
|
||
// The callback that OpenSSL will call when generating the session ticket | ||
// and it needs to collect additional application specific data. | ||
static int GenerateCallback(SSL* ssl, void* arg); | ||
|
||
// The callback that OpenSSL will call when consuming the session ticket | ||
// and it needs to pass embedded application data back into the app. | ||
static SSL_TICKET_RETURN DecryptedCallback(SSL* ssl, | ||
SSL_SESSION* session, | ||
const unsigned char* keyname, | ||
size_t keyname_len, | ||
SSL_TICKET_STATUS status, | ||
void* arg); | ||
|
||
private: | ||
Store ticket_; | ||
Store transport_params_; | ||
}; | ||
|
||
// SessionTicket::AppData is a utility class that is used only during the | ||
// generation or access of TLS stateless sesson tickets. It exists solely to | ||
// provide a easier way for Session::Application instances to set relevant | ||
// metadata in the session ticket when it is created, and the exract and | ||
// subsequently verify that data when a ticket is received and is being | ||
// validated. The app data is completely opaque to anything other than the | ||
// server-side of the Session::Application that sets it. | ||
class SessionTicket::AppData final { | ||
public: | ||
enum class Status { | ||
TICKET_IGNORE = SSL_TICKET_RETURN_IGNORE, | ||
TICKET_IGNORE_RENEW = SSL_TICKET_RETURN_IGNORE_RENEW, | ||
TICKET_USE = SSL_TICKET_RETURN_USE, | ||
TICKET_USE_RENEW = SSL_TICKET_RETURN_USE_RENEW, | ||
}; | ||
|
||
explicit AppData(SSL* session); | ||
AppData(const AppData&) = delete; | ||
AppData(AppData&&) = delete; | ||
AppData& operator=(const AppData&) = delete; | ||
AppData& operator=(AppData&&) = delete; | ||
|
||
bool Set(const uv_buf_t& data); | ||
std::optional<const uv_buf_t> Get() const; | ||
|
||
// A source of application data collected during the creation of the | ||
// session ticket. This interface will be implemented by the QUIC | ||
// Session. | ||
class Source { | ||
public: | ||
enum class Flag { STATUS_NONE, STATUS_RENEW }; | ||
|
||
// Collect application data into the given AppData instance. | ||
virtual void CollectSessionTicketAppData(AppData* app_data) const = 0; | ||
|
||
// Extract application data from the given AppData instance. | ||
virtual Status ExtractSessionTicketAppData( | ||
const AppData& app_data, Flag flag = Flag::STATUS_NONE) = 0; | ||
}; | ||
|
||
static void Collect(SSL* ssl); | ||
static Status Extract(SSL* ssl); | ||
|
||
private: | ||
bool set_ = false; | ||
SSL* ssl_; | ||
}; | ||
|
||
} // namespace quic | ||
} // namespace node | ||
|
||
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC | ||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS |
Oops, something went wrong.