Skip to content

Commit c8cc583

Browse files
committed
quic: add additional quic implementation utilities
* add TokenSecret, StatelessResetToken, RetryToken, and RegularToken * add SessionTicket implementation
1 parent 09a4bb1 commit c8cc583

File tree

8 files changed

+960
-0
lines changed

8 files changed

+960
-0
lines changed

node.gyp

+5
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,13 @@
339339
'src/quic/cid.cc',
340340
'src/quic/data.cc',
341341
'src/quic/preferredaddress.cc',
342+
'src/quic/sessionticket.cc',
343+
'src/quic/tokens.cc',
342344
'src/quic/cid.h',
343345
'src/quic/data.h',
344346
'src/quic/preferredaddress.h',
347+
'src/quic/sessionticket.h',
348+
'src/quic/tokens.h',
345349
],
346350
'node_mksnapshot_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)node_mksnapshot<(EXECUTABLE_SUFFIX)',
347351
'conditions': [
@@ -1033,6 +1037,7 @@
10331037
'test/cctest/test_crypto_clienthello.cc',
10341038
'test/cctest/test_node_crypto.cc',
10351039
'test/cctest/test_quic_cid.cc',
1040+
'test/cctest/test_quic_tokens.cc',
10361041
]
10371042
}],
10381043
['v8_enable_inspector==1', {

src/quic/data.cc

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ using v8::BigInt;
1515
using v8::Integer;
1616
using v8::Local;
1717
using v8::MaybeLocal;
18+
using v8::Uint8Array;
1819
using v8::Undefined;
1920
using v8::Value;
2021

@@ -66,6 +67,14 @@ Store::Store(v8::Local<v8::ArrayBufferView> view, Option option)
6667
}
6768
}
6869

70+
v8::Local<v8::Uint8Array> Store::ToUint8Array(Environment* env) const {
71+
return !store_
72+
? Uint8Array::New(v8::ArrayBuffer::New(env->isolate(), 0), 0, 0)
73+
: Uint8Array::New(v8::ArrayBuffer::New(env->isolate(), store_),
74+
offset_,
75+
length_);
76+
}
77+
6978
Store::operator bool() const {
7079
return store_ != nullptr;
7180
}

src/quic/data.h

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
44
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
55

6+
#include <env.h>
67
#include <memory_tracker.h>
78
#include <nghttp3/nghttp3.h>
89
#include <ngtcp2/ngtcp2.h>
@@ -41,6 +42,8 @@ class Store final : public MemoryRetainer {
4142
Store(v8::Local<v8::ArrayBuffer> buffer, Option option = Option::NONE);
4243
Store(v8::Local<v8::ArrayBufferView> view, Option option = Option::NONE);
4344

45+
v8::Local<v8::Uint8Array> ToUint8Array(Environment* env) const;
46+
4447
operator uv_buf_t() const;
4548
operator ngtcp2_vec() const;
4649
operator nghttp3_vec() const;

src/quic/sessionticket.cc

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
2+
3+
#include "sessionticket.h"
4+
#include <env-inl.h>
5+
#include <memory_tracker-inl.h>
6+
#include <ngtcp2/ngtcp2_crypto.h>
7+
#include <node_buffer.h>
8+
#include <node_errors.h>
9+
10+
namespace node {
11+
12+
using v8::ArrayBufferView;
13+
using v8::Just;
14+
using v8::Local;
15+
using v8::Maybe;
16+
using v8::MaybeLocal;
17+
using v8::Nothing;
18+
using v8::Object;
19+
using v8::Value;
20+
using v8::ValueDeserializer;
21+
using v8::ValueSerializer;
22+
23+
namespace quic {
24+
25+
namespace {
26+
SessionTicket::AppData::Source* GetAppDataSource(SSL* ssl) {
27+
ngtcp2_crypto_conn_ref* ref =
28+
static_cast<ngtcp2_crypto_conn_ref*>(SSL_get_app_data(ssl));
29+
if (ref != nullptr && ref->user_data != nullptr) {
30+
return static_cast<SessionTicket::AppData::Source*>(ref->user_data);
31+
}
32+
return nullptr;
33+
}
34+
} // namespace
35+
36+
SessionTicket::SessionTicket(Store&& ticket, Store&& transport_params)
37+
: ticket_(std::move(ticket)),
38+
transport_params_(std::move(transport_params)) {}
39+
40+
Maybe<SessionTicket> SessionTicket::FromV8Value(Environment* env,
41+
v8::Local<v8::Value> value) {
42+
if (!value->IsArrayBufferView()) {
43+
THROW_ERR_INVALID_ARG_TYPE(env, "The ticket must be an ArrayBufferView.");
44+
return Nothing<SessionTicket>();
45+
}
46+
47+
Store content(value.As<ArrayBufferView>());
48+
ngtcp2_vec vec = content;
49+
50+
ValueDeserializer des(env->isolate(), vec.base, vec.len);
51+
52+
if (des.ReadHeader(env->context()).IsNothing()) {
53+
THROW_ERR_INVALID_ARG_VALUE(env, "The ticket format is invalid.");
54+
return Nothing<SessionTicket>();
55+
}
56+
57+
Local<Value> ticket;
58+
Local<Value> transport_params;
59+
60+
errors::TryCatchScope tryCatch(env);
61+
62+
if (!des.ReadValue(env->context()).ToLocal(&ticket) ||
63+
!des.ReadValue(env->context()).ToLocal(&transport_params) ||
64+
!ticket->IsArrayBufferView() || !transport_params->IsArrayBufferView()) {
65+
if (tryCatch.HasCaught()) {
66+
// Any errors thrown we want to catch and supress. The only
67+
// error we want to expose to the user is that the ticket format
68+
// is invalid.
69+
if (!tryCatch.HasTerminated()) {
70+
THROW_ERR_INVALID_ARG_VALUE(env, "The ticket format is invalid.");
71+
tryCatch.ReThrow();
72+
}
73+
return Nothing<SessionTicket>();
74+
}
75+
THROW_ERR_INVALID_ARG_VALUE(env, "The ticket format is invalid.");
76+
return Nothing<SessionTicket>();
77+
}
78+
79+
return Just(SessionTicket(Store(ticket.As<ArrayBufferView>()),
80+
Store(transport_params.As<ArrayBufferView>())));
81+
}
82+
83+
MaybeLocal<Object> SessionTicket::encode(Environment* env) const {
84+
auto context = env->context();
85+
ValueSerializer ser(env->isolate());
86+
ser.WriteHeader();
87+
88+
if (ser.WriteValue(context, ticket_.ToUint8Array(env)).IsNothing() ||
89+
ser.WriteValue(context, transport_params_.ToUint8Array(env))
90+
.IsNothing()) {
91+
return MaybeLocal<Object>();
92+
}
93+
94+
auto result = ser.Release();
95+
96+
return Buffer::New(env, reinterpret_cast<char*>(result.first), result.second);
97+
}
98+
99+
const uv_buf_t SessionTicket::ticket() const {
100+
return ticket_;
101+
}
102+
103+
const ngtcp2_vec SessionTicket::transport_params() const {
104+
return transport_params_;
105+
}
106+
107+
void SessionTicket::MemoryInfo(MemoryTracker* tracker) const {
108+
tracker->TrackField("ticket", ticket_);
109+
tracker->TrackField("transport_params", transport_params_);
110+
}
111+
112+
int SessionTicket::GenerateCallback(SSL* ssl, void* arg) {
113+
SessionTicket::AppData::Collect(ssl);
114+
return 1;
115+
}
116+
117+
SSL_TICKET_RETURN SessionTicket::DecryptedCallback(SSL* ssl,
118+
SSL_SESSION* session,
119+
const unsigned char* keyname,
120+
size_t keyname_len,
121+
SSL_TICKET_STATUS status,
122+
void* arg) {
123+
switch (status) {
124+
default:
125+
return SSL_TICKET_RETURN_IGNORE;
126+
case SSL_TICKET_EMPTY:
127+
[[fallthrough]];
128+
case SSL_TICKET_NO_DECRYPT:
129+
return SSL_TICKET_RETURN_IGNORE_RENEW;
130+
case SSL_TICKET_SUCCESS_RENEW:
131+
[[fallthrough]];
132+
case SSL_TICKET_SUCCESS:
133+
return static_cast<SSL_TICKET_RETURN>(
134+
SessionTicket::AppData::Extract(ssl));
135+
}
136+
}
137+
138+
SessionTicket::AppData::AppData(SSL* ssl) : ssl_(ssl) {}
139+
140+
bool SessionTicket::AppData::Set(const uv_buf_t& data) {
141+
if (set_ || data.base == nullptr || data.len == 0) return false;
142+
set_ = true;
143+
SSL_SESSION_set1_ticket_appdata(SSL_get0_session(ssl_), data.base, data.len);
144+
return set_;
145+
}
146+
147+
std::optional<const uv_buf_t> SessionTicket::AppData::Get() const {
148+
uv_buf_t buf;
149+
int ret =
150+
SSL_SESSION_get0_ticket_appdata(SSL_get0_session(ssl_),
151+
reinterpret_cast<void**>(&buf.base),
152+
reinterpret_cast<size_t*>(&buf.len));
153+
if (ret != 1) return std::nullopt;
154+
return buf;
155+
}
156+
157+
void SessionTicket::AppData::Collect(SSL* ssl) {
158+
auto source = GetAppDataSource(ssl);
159+
if (source != nullptr) {
160+
SessionTicket::AppData app_data(ssl);
161+
source->CollectSessionTicketAppData(&app_data);
162+
}
163+
}
164+
165+
SessionTicket::AppData::Status SessionTicket::AppData::Extract(SSL* ssl) {
166+
auto source = GetAppDataSource(ssl);
167+
if (source != nullptr) {
168+
SessionTicket::AppData app_data(ssl);
169+
return source->ExtractSessionTicketAppData(app_data);
170+
}
171+
return Status::TICKET_IGNORE;
172+
}
173+
174+
} // namespace quic
175+
} // namespace node
176+
177+
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC

src/quic/sessionticket.h

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#pragma once
2+
3+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
4+
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
5+
6+
#include <crypto/crypto_common.h>
7+
#include <env.h>
8+
#include <memory_tracker.h>
9+
#include <uv.h>
10+
#include <v8.h>
11+
#include "data.h"
12+
13+
namespace node {
14+
namespace quic {
15+
16+
// A TLS 1.3 Session resumption ticket. Encapsulates both the TLS
17+
// ticket and the encoded QUIC transport parameters. The encoded
18+
// structure should be considered to be opaque for end users.
19+
// In JavaScript, the ticket will be represented as a Buffer
20+
// instance with opaque data. To resume a session, the user code
21+
// would pass that Buffer back into to client connection API.
22+
class SessionTicket final : public MemoryRetainer {
23+
public:
24+
static v8::Maybe<SessionTicket> FromV8Value(Environment* env,
25+
v8::Local<v8::Value> value);
26+
27+
SessionTicket() = default;
28+
SessionTicket(Store&& ticket, Store&& transport_params);
29+
30+
const uv_buf_t ticket() const;
31+
32+
const ngtcp2_vec transport_params() const;
33+
34+
v8::MaybeLocal<v8::Object> encode(Environment* env) const;
35+
36+
void MemoryInfo(MemoryTracker* tracker) const override;
37+
SET_MEMORY_INFO_NAME(SessionTicket)
38+
SET_SELF_SIZE(SessionTicket)
39+
40+
class AppData;
41+
42+
// The callback that OpenSSL will call when generating the session ticket
43+
// and it needs to collect additional application specific data.
44+
static int GenerateCallback(SSL* ssl, void* arg);
45+
46+
// The callback that OpenSSL will call when consuming the session ticket
47+
// and it needs to pass embedded application data back into the app.
48+
static SSL_TICKET_RETURN DecryptedCallback(SSL* ssl,
49+
SSL_SESSION* session,
50+
const unsigned char* keyname,
51+
size_t keyname_len,
52+
SSL_TICKET_STATUS status,
53+
void* arg);
54+
55+
private:
56+
Store ticket_;
57+
Store transport_params_;
58+
};
59+
60+
// SessionTicket::AppData is a utility class that is used only during the
61+
// generation or access of TLS stateless sesson tickets. It exists solely to
62+
// provide a easier way for Session::Application instances to set relevant
63+
// metadata in the session ticket when it is created, and the exract and
64+
// subsequently verify that data when a ticket is received and is being
65+
// validated. The app data is completely opaque to anything other than the
66+
// server-side of the Session::Application that sets it.
67+
class SessionTicket::AppData final {
68+
public:
69+
enum class Status {
70+
TICKET_IGNORE = SSL_TICKET_RETURN_IGNORE,
71+
TICKET_IGNORE_RENEW = SSL_TICKET_RETURN_IGNORE_RENEW,
72+
TICKET_USE = SSL_TICKET_RETURN_USE,
73+
TICKET_USE_RENEW = SSL_TICKET_RETURN_USE_RENEW,
74+
};
75+
76+
explicit AppData(SSL* session);
77+
AppData(const AppData&) = delete;
78+
AppData(AppData&&) = delete;
79+
AppData& operator=(const AppData&) = delete;
80+
AppData& operator=(AppData&&) = delete;
81+
82+
bool Set(const uv_buf_t& data);
83+
std::optional<const uv_buf_t> Get() const;
84+
85+
// A source of application data collected during the creation of the
86+
// session ticket. This interface will be implemented by the QUIC
87+
// Session.
88+
class Source {
89+
public:
90+
enum class Flag { STATUS_NONE, STATUS_RENEW };
91+
92+
// Collect application data into the given AppData instance.
93+
virtual void CollectSessionTicketAppData(AppData* app_data) const = 0;
94+
95+
// Extract application data from the given AppData instance.
96+
virtual Status ExtractSessionTicketAppData(
97+
const AppData& app_data, Flag flag = Flag::STATUS_NONE) = 0;
98+
};
99+
100+
static void Collect(SSL* ssl);
101+
static Status Extract(SSL* ssl);
102+
103+
private:
104+
bool set_ = false;
105+
SSL* ssl_;
106+
};
107+
108+
} // namespace quic
109+
} // namespace node
110+
111+
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
112+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

0 commit comments

Comments
 (0)