Coverage Report

Created: 2025-09-05 10:05

/src/node/src/quic/tokens.h
Line
Count
Source (jump to first uncovered line)
1
#pragma once
2
3
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
4
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
5
6
#include <memory_tracker.h>
7
#include <ngtcp2/ngtcp2_crypto.h>
8
#include <node_internals.h>
9
#include <node_sockaddr.h>
10
#include "cid.h"
11
12
namespace node {
13
namespace quic {
14
15
// TokenSecrets are used to generate things like stateless reset tokens,
16
// retry tokens, and token packets. They are always QUIC_TOKENSECRET_LEN
17
// bytes in length.
18
//
19
// In the default case, token secrets will always be generated randomly.
20
// User code will be given the option to provide a secret directly
21
// however.
22
class TokenSecret final : public MemoryRetainer {
23
 public:
24
  static constexpr int QUIC_TOKENSECRET_LEN = 16;
25
26
  // Generate a random secret.
27
  TokenSecret();
28
29
  // Copy the given secret. The uint8_t* is assumed
30
  // to be QUIC_TOKENSECRET_LEN in length. Note that
31
  // the length is not verified so care must be taken
32
  // when this constructor is used.
33
  explicit TokenSecret(const uint8_t* secret);
34
  ~TokenSecret();
35
36
0
  TokenSecret(const TokenSecret&) = default;
37
0
  TokenSecret& operator=(const TokenSecret&) = default;
38
  TokenSecret(TokenSecret&&) = delete;
39
  TokenSecret& operator=(TokenSecret&&) = delete;
40
41
  operator const uint8_t*() const;
42
  uint8_t operator[](int pos) const;
43
44
  std::string ToString() const;
45
46
  SET_NO_MEMORY_INFO()
47
  SET_MEMORY_INFO_NAME(TokenSecret)
48
  SET_SELF_SIZE(TokenSecret)
49
50
 private:
51
  operator const char*() const;
52
  uint8_t buf_[QUIC_TOKENSECRET_LEN];
53
};
54
55
// A stateless reset token is used when a QUIC endpoint receives a QUIC packet
56
// with a short header but the associated connection ID cannot be matched to any
57
// known Session. In such cases, the receiver may choose to send a subtle opaque
58
// indication to the sending peer that state for the Session has apparently been
59
// lost. For any on- or off- path attacker, a stateless reset packet resembles
60
// any other QUIC packet with a short header. In order to be successfully
61
// handled as a stateless reset, the peer must have already seen a reset token
62
// issued to it associated with the given CID. The token itself is opaque to the
63
// peer that receives is but must be possible to statelessly recreate by the
64
// peer that originally created it. The actual implementation is Node.js
65
// specific but we currently defer to a utility function provided by ngtcp2.
66
//
67
// QUIC leaves the generation of stateless session tokens up to the
68
// implementation to figure out. The idea, however, is that it ought to be
69
// possible to generate a stateless reset token reliably even when all state
70
// for a connection has been lost. We use the cid as it is the only reliably
71
// consistent bit of data we have when a session is destroyed.
72
//
73
// StatlessResetTokens are always kStatelessTokenLen bytes,
74
// as are the secrets used to generate the token.
75
class StatelessResetToken final : public MemoryRetainer {
76
 public:
77
  static constexpr int kStatelessTokenLen = NGTCP2_STATELESS_RESET_TOKENLEN;
78
79
  StatelessResetToken();
80
81
  // Generates a stateless reset token using HKDF with the cid and token secret
82
  // as input. The token secret is either provided by user code when an Endpoint
83
  // is created or is generated randomly.
84
  StatelessResetToken(const TokenSecret& secret, const CID& cid);
85
86
  // Generates a stateless reset token using the given token storage.
87
  // The StatelessResetToken wraps the token and does not take ownership.
88
  // The token storage must be at least kStatelessTokenLen bytes in length.
89
  // The length is not verified so care must be taken when using this
90
  // constructor.
91
  StatelessResetToken(uint8_t* token,
92
                      const TokenSecret& secret,
93
                      const CID& cid);
94
95
  // Wraps the given token. Does not take over ownership of the token storage.
96
  // The token must be at least kStatelessTokenLen bytes in length.
97
  // The length is not verified so care must be taken when using this
98
  // constructor.
99
  explicit StatelessResetToken(const uint8_t* token);
100
101
  StatelessResetToken(const StatelessResetToken& other);
102
  StatelessResetToken(StatelessResetToken&&) = delete;
103
104
  std::string ToString() const;
105
106
  operator const uint8_t*() const;
107
  operator bool() const;
108
109
  bool operator==(const StatelessResetToken& other) const;
110
  bool operator!=(const StatelessResetToken& other) const;
111
112
  struct Hash {
113
    size_t operator()(const StatelessResetToken& token) const;
114
  };
115
116
  SET_NO_MEMORY_INFO()
117
  SET_MEMORY_INFO_NAME(StatelessResetToken)
118
  SET_SELF_SIZE(StatelessResetToken)
119
120
  template <typename T>
121
  using Map =
122
      std::unordered_map<StatelessResetToken, T, StatelessResetToken::Hash>;
123
124
  static StatelessResetToken kInvalid;
125
126
 private:
127
  operator const char*() const;
128
129
  const uint8_t* ptr_;
130
  uint8_t buf_[NGTCP2_STATELESS_RESET_TOKENLEN];
131
};
132
133
// A RETRY packet communicates a retry token to the client. Retry tokens are
134
// generated only by QUIC servers for the purpose of validating the network path
135
// between a client and server. The content payload of the RETRY packet is
136
// opaque to the clientand must not be guessable by on- or off-path attackers.
137
//
138
// A QUIC server sends a RETRY token as a way of initiating explicit path
139
// validation in response to an initial QUIC packet. The client, upon receiving
140
// a RETRY, must abandon the initial connection attempt and try again with the
141
// received retry token included with the new initial packet sent to the server.
142
// If the server is performing explicit validation, it will look for the
143
// presence of the retry token and attempt to validate it if found. The internal
144
// structure of the retry token must be meaningful to the server, and the server
145
// must be able to validate that the token is correct without relying on any
146
// state left over from the previous connection attempt. We use an
147
// implementation that is provided by ngtcp2.
148
//
149
// The token secret must be kept private on the QUIC server that generated the
150
// retry. When multiple QUIC servers are used in a cluster, it cannot be
151
// guaranteed that the same QUIC server instance will receive the subsequent new
152
// Initial packet. Therefore, all QUIC servers in the cluster should either
153
// share or be aware of the same token secret or a mechanism needs to be
154
// implemented to ensure that subsequent packets are routed to the same QUIC
155
// server instance.
156
class RetryToken final : public MemoryRetainer {
157
 public:
158
  // The token prefix that is used to differentiate between a retry token
159
  // and a regular token.
160
  static constexpr uint8_t kTokenMagic = NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY;
161
  static constexpr int kRetryTokenLen = NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN;
162
163
  static constexpr uint64_t QUIC_DEFAULT_RETRYTOKEN_EXPIRATION =
164
      10 * NGTCP2_SECONDS;
165
  static constexpr uint64_t QUIC_MIN_RETRYTOKEN_EXPIRATION = 1 * NGTCP2_SECONDS;
166
167
  // Generates a new retry token.
168
  RetryToken(uint32_t version,
169
             const SocketAddress& address,
170
             const CID& retry_cid,
171
             const CID& odcid,
172
             const TokenSecret& token_secret);
173
174
  // Wraps the given retry token
175
  RetryToken(const uint8_t* token, size_t length);
176
177
  // Validates the retry token given the input. If the token is valid,
178
  // the embedded original CID will be extracted from the token an
179
  // returned. If the token is invalid, std::nullopt will be returned.
180
  std::optional<CID> Validate(
181
      uint32_t version,
182
      const SocketAddress& address,
183
      const CID& cid,
184
      const TokenSecret& token_secret,
185
      uint64_t verification_expiration = QUIC_DEFAULT_RETRYTOKEN_EXPIRATION);
186
187
  operator const ngtcp2_vec&() const;
188
  operator const ngtcp2_vec*() const;
189
  operator bool() const;
190
191
  std::string ToString() const;
192
193
  SET_NO_MEMORY_INFO()
194
  SET_MEMORY_INFO_NAME(RetryToken)
195
  SET_SELF_SIZE(RetryToken)
196
197
 private:
198
  operator const char*() const;
199
  uint8_t buf_[kRetryTokenLen];
200
  const ngtcp2_vec ptr_;
201
};
202
203
// A NEW_TOKEN packet communicates a regular token to a client that the server
204
// would like the client to send in the header of an initial packet for a
205
// future connection. It is similar to RETRY and used for the same purpose,
206
// except a NEW_TOKEN is used in advance of the client establishing a new
207
// connection and a RETRY is sent in response to the client trying to open
208
// a new connection.
209
class RegularToken final : public MemoryRetainer {
210
 public:
211
  // The token prefix that is used to differentiate between a retry token
212
  // and a regular token.
213
  static constexpr uint8_t kTokenMagic = NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR;
214
  static constexpr int kRegularTokenLen = NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN;
215
  static constexpr uint64_t QUIC_DEFAULT_REGULARTOKEN_EXPIRATION =
216
      10 * NGTCP2_SECONDS;
217
  static constexpr uint64_t QUIC_MIN_REGULARTOKEN_EXPIRATION =
218
      1 * NGTCP2_SECONDS;
219
220
  RegularToken();
221
222
  // Generates a new retry token.
223
  RegularToken(uint32_t version,
224
               const SocketAddress& address,
225
               const TokenSecret& token_secret);
226
227
  // Wraps the given retry token
228
  RegularToken(const uint8_t* token, size_t length);
229
230
  // Validates the retry token given the input.
231
  bool Validate(
232
      uint32_t version,
233
      const SocketAddress& address,
234
      const TokenSecret& token_secret,
235
      uint64_t verification_expiration = QUIC_DEFAULT_REGULARTOKEN_EXPIRATION);
236
237
  operator const ngtcp2_vec&() const;
238
  operator const ngtcp2_vec*() const;
239
240
  operator bool() const;
241
242
  std::string ToString() const;
243
244
  SET_NO_MEMORY_INFO()
245
  SET_MEMORY_INFO_NAME(RetryToken)
246
  SET_SELF_SIZE(RetryToken)
247
248
 private:
249
  operator const char*() const;
250
  uint8_t buf_[kRegularTokenLen];
251
  const ngtcp2_vec ptr_;
252
};
253
254
}  // namespace quic
255
}  // namespace node
256
257
#endif  // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
258
#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS