Coverage Report

Created: 2025-12-30 08:42

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