• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
GetAppDataSource(SSL * ssl)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 
SessionTicket(Store && ticket,Store && transport_params)36 SessionTicket::SessionTicket(Store&& ticket, Store&& transport_params)
37     : ticket_(std::move(ticket)),
38       transport_params_(std::move(transport_params)) {}
39 
FromV8Value(Environment * env,v8::Local<v8::Value> value)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 suppress. 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 
encode(Environment * env) const83 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 
ticket() const99 const uv_buf_t SessionTicket::ticket() const {
100   return ticket_;
101 }
102 
transport_params() const103 const ngtcp2_vec SessionTicket::transport_params() const {
104   return transport_params_;
105 }
106 
MemoryInfo(MemoryTracker * tracker) const107 void SessionTicket::MemoryInfo(MemoryTracker* tracker) const {
108   tracker->TrackField("ticket", ticket_);
109   tracker->TrackField("transport_params", transport_params_);
110 }
111 
GenerateCallback(SSL * ssl,void * arg)112 int SessionTicket::GenerateCallback(SSL* ssl, void* arg) {
113   SessionTicket::AppData::Collect(ssl);
114   return 1;
115 }
116 
DecryptedCallback(SSL * ssl,SSL_SESSION * session,const unsigned char * keyname,size_t keyname_len,SSL_TICKET_STATUS status,void * arg)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 
AppData(SSL * ssl)138 SessionTicket::AppData::AppData(SSL* ssl) : ssl_(ssl) {}
139 
Set(const uv_buf_t & data)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 
Get() const147 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 
Collect(SSL * ssl)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 
Extract(SSL * ssl)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
178